网络版计算器
抓包:tcpdump -i any -nn tcp port 8080
参数解释: -i:表示想要抓那个网络 -nn:第一个n表示能把主机名用数字表示的都用数字表示,第二个n表示把更多的信息能用数字表示的用数字表示。 tcp:表示想要抓的协议 any:任意端口 8080:服务器端口号 Protocol.hpp 定义通信的结构体
#ifndef __PROTOCOL_HPP__
#define __PROTOCOL_HPP__
#include<iostream>
typedef struct Request{
int x;
int y;
char op;
}Request;
typedef struct Response{
int code;
int result;
}Response;
#endif
Server.hpp
#ifndef __SERVER_HPP__
#define __SERVER_HPP__
#include <iostream>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include "Protocol.hpp"
using namespace std;
class server{
private:
int port;
int lsock;
public:
server(int _p):port(_p),lsock(-1)
{}
void initServer()
{
lsock = socket(AF_INET, SOCK_STREAM, 0);
if( lsock < 0 )
{
cerr << "socket error " << endl;
exit(2);
}
struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_port = htons(port);
local.sin_addr.s_addr = INADDR_ANY;
if(bind(lsock, (struct sockaddr*)&local, sizeof(local)) < 0){
cerr << "bind error!" << endl;
exit(3);
}
if(listen(lsock, 5) < 0){
cerr << "listen error!\n" << endl;
}
}
void cal(int sock)
{
Request rq;给服务器发结构化数据rq
Response rsp={4, 0};
ssize_t s = recv(sock, &rq, sizeof(rq), 0);从sock里读的内容放到rq,期望读sizeof(rq),以默认的阻塞方式读取,收到rq
if(s > 0){
rsp.code = 0;
switch(rq.op){
case '+':
rsp.result = rq.x + rq.y;
break;
case '-':
rsp.result = rq.x - rq.y;
break;
case '*':
rsp.result = rq.x * rq.y;
break;
case '/':
if(rq.y != 0){
rsp.result = rq.x / rq.y;
}
else{
rsp.code = 1;
}
break;
case '%':
if(rq.y != 0){
rsp.result = rq.x + rq.y;
}
else{
rsp.code = 2;
}
break;
default:
rsp.code = 3;
break;
}
}
send(sock, &rsp, sizeof(rsp), 0);
close(sock);
}
void start()
{
struct sockaddr_in peer;
for(;;){
socklen_t len = sizeof(peer);
int sock = accept(lsock, (struct sockaddr*)&peer,&len);
if( sock < 0 ){
cerr << "accept error!" << endl;
continue;
}
if(fork() == 0){
if(fork() > 0){
exit(0);
}
close(lsock);
cal(sock);
exit(0);
}
close(sock);
waitpid(-1, nullptr, 0);
}
}
~server()
{
close(lsock);
}
};
#endif
Server.cc
#include"Server.hpp"
void Menu(string str)
{
cout<<"Usage: "<<endl;
cout<<str<<"server port"<<endl;
}
int main(int argc,char* argv[])
{
if(argc!=2)
{
Menu(argv[0]);
exit(1);
}
server *cp=new server(atoi(argv[1]));端口号
cp->initServer();
cp->start();
delete cp;
return 0;
}
Client.hpp
#ifndef __CLIENT_HPP__
#define __CLIENT_HPP__
#include<iostream>
#include<unistd.h>
#include<cstdlib>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<sys/wait.h>
#include"Protocol.hpp"
class client{
private:
std::string ip;
int port;
int sock;
public:
client(std::string _ip,int _port)
:ip(_ip),port(_port),sock(-1)
{}
void initclient()
{
sock = socket(AF_INET,SOCK_STREAM,0);
if(sock < 0){
std::cerr<<"socket error!"<<std::endl;
exit(2);
}
}
void start()
{
struct sockaddr_in server;
server.sin_family = AF_INET;
server.sin_port = htons(port);
server.sin_addr.s_addr = inet_addr(ip.c_str());
if(connect(sock,(struct sockaddr*)&server,sizeof(server)) < 0){通过sock连接server
std::cerr << "connect error"<<std::endl;
exit(3);
}
Request rq;
Response rsp;
std::cout << "date1# ";
std::cin >> rq.x;
std::cout << "date2# ";
std::cin >> rq.y;
std::cout << "op# ";
std::cin >> rq.op;
rq看作2进制流发送,
send(sock,&rq,sizeof(rq),0);把rq往sock里塞,
ssize_t s = recv(sock,&rsp,sizeof(rsp),0);
if(s > 0){
std:: cout <<"code : "<< rsp.code <<std::endl;
std::cout <<"result : "<<rsp.result << std::endl;
}
}
~client()
{
close(sock);
}
};
#endif
Client.cc
#include"Client.hpp"
void Menu(string str)
{
cout<<"Usage: "<<endl;
cout<<str<<"server ip and port"<<endl;
}
int main(int argc,char* argv[])
{
if(argc!=3)
{
Menu(argv[0]);
exit(1);
}
client *cp=new client(argv[1],atoi(argv[2]));IP和端口号
cp->initClient();
cp->start();
delete cp;
return 0;
}
HTTP
HTTP特征: 无连接:TCP建立连接和http无关,http直接向对方发送http request即可 无状态:http本身是无状态的,并不会记录任何用户信息,真正记录基本信息的技术:cookie+session 简单快速
简易HTTP服务器
#pragma once
#include <iostream>
#include <unistd.h>
#include <stdlib.h>
#include <strings.h>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include<signal.h>
#define BACKLOG 5
using namespace std;
class httpServer{
private:
int port;
int lsock;
public:
httpServer(int _p):port(_p),lsock(-1)
{}
void InitServer()
{
signal(SIGCHLD,SIG_IGN);父进程忽略子进程发的的信号
lsock = socket(AF_INET, SOCK_STREAM, 0);
if( lsock < 0 )
{
cerr << "socket error " << endl;
exit(2);
}
struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_port = htons(port);
local.sin_addr.s_addr = INADDR_ANY;
if(bind(lsock, (struct sockaddr*)&local, sizeof(local)) < 0){
cerr << "bind error!" << endl;
exit(3);
}
if(listen(lsock, BACKLOG) < 0)
{ cerr << "listen error"<<endl;
exit(4);
}
}
void Echo_http(int sock)
{
char request[2048];
ssize_t s = recv(sock,request,sizeof(request),0);
if(s > 0)
request[s] = 0;
cout << request << endl;打印请求
开始响应:
string response = "HTTP/1.0 200 OK\r\n";
response += "Content-type: text/html\r\n";
response += "\r\n";
response += "\
<!DOCTYPE html>\
<html>\
<head>\
<title>192.168.31.223</title>\
</head>\
<body>\
<h1>Welcome</h1>\
<p>helloworld</p>\
</body>\
</html>\
";
send(sock,response.c_str(),response.size(),0);
}给response往sock发送
close(sock);短连接:1发1收即结束
}
void start()启动服务器
{
struct sockaddr_in peer;
for(;;){
socklen_t len = sizeof(peer);
int sock = accept(lsock, (struct sockaddr*)&peer,&len);
从peer接收lsock;&:取地址;获得新链接时,新链接的套接字信息在&peer里保存
if( sock < 0 ){
cerr << "accept error!" << endl;
continue;
}
if(fork() == 0){
close(lsock);子进程处理链接
Echo_http(sock);
exit(0);任务处理完,退出
}
close(sock);父进程
}
}
~httpServer()
{
close(lsock);
}
};
HttpServer.cc:
#include "httpserver.hpp"
static void Usage(string proc)
{cout c< "Usage : \n\t" ;
cout << proc< " port" << endl;}
int main ( int argc ,char *argv[])
if(argc !=2)参数传入1个,可执行行程序名1个
{
usage(argv[0]);
exit ( 1);
}
HttpServer *hp = new HttpServer(atoi(argv[1]));
hp->InitServer( );
hp->start();
return 0;
TCP协议段格式
32位序号:可靠传输。TCP将要传输的每个字节都进行了编号,序号是本报文段发送的数据组的第一个字节的编号,序号可以保证传输信息的有效性。(解决按序到达问题) 32位确认序号:确认号只有当ACK标志为1时才有效。(解决确认应答问题) 客户端发送fin之后,server没有关闭文件描述符,挂满CLOSE_WAIT时,消耗资源;文件描述符生命周期随进程,进程结束,自动释放; 有了TIME_WAIT状态,即使不能够完全的确定是否在特定的时间内server端有接收到ACK信号,但是可以确认的是如果没有接收到,client端会再次收到FIN的信号,所以这里可以理解为在特定的时间段内”没有消息便是好消息”,所以TIME_WAIT最大的作用有两个①尽量保证最后一个ACK被对方收到,双方是正常状态下断开,尽快释放服务器的资源(那么如果你的ACK就是丢了,server端在多次重传下,依旧无法完成四次挥手,那么肯定是网络出现了问题,此时就会强制的关闭连接)②等待历史数据在网络上进行消散,发送的数据全都被收到 主动断开连接的一方要进入TIME_WAIT状态,连接没有完全释放,不能立即启动,端口复用函数:立即启动 TIME_WAIT的时间:2MSL(最大传送时间—数据从一方发送到另外一方所花费的最大时间)。因为不管是超时重传还是等待历史数据在网络中进行消散2MSL已经足够了 客户端退出,服务器启动,客户端连接,服务器不能启动,再过一段时间(TIME_WAIT时间段过去之后),可以启动。 那么既然快重传即快又能重传,超时重传呢?原因是他们两个之间其实是互相补充的,并不矛盾。如果ACK都丢了,那么也就不可能收到三个连续重复的确认应答信号,所以超时重传是一种兜底,少于3次应答的情况时,使用超时重传 3次握手:最后1次ack丢失时,1方客户端建立连接,1方没有连接,客户端:背锅:浪费资源, 4次挥手: 面这样的拥塞窗口增长速度, 是指数级别的. “慢启动” 只是指初使时慢, 但是增长速度非常快; 前期满
|