TCP服务器搭建(代码实现)及SO_REUSEADDR解析

来源:互联网 发布:java调用百度地图定位 编辑:程序博客网 时间:2024/05/08 08:38
<span style="font-size:18px;"></span>

相信读者对TCP协议应该有一个清晰的概念,故而在此不再细说,但在后面还是会详细的给大家介绍,首先,先给大家说一说搭建TCP服务器用的主要函数:

首先是网络字节序和主机字节序的转换函数


其次是



在给大家看一看监听函数


还有bind和accept函数就不再给大家介绍了


server的搭建有基于进程的和基于线程的,我在这里给大家的是基于线程的。

在这里给大家说一说,多进程服务器,反应慢 ,      吃资源,多线程线程服务器,线程 有上线, CPU负载过大。各有优点和好处。

<span style="font-size:18px;">server#include<stdio.h>#include<sys/socket.h>#include<errno.h>#include<string.h>#include<sys/types.h>#include<unistd.h>#include<netinet/in.h>#include<arpa/inet.h>#include<stdlib.h>#include<pthread.h>#define _PORT_ 8080#define _BACKLOG_ 10static void guse(const char* prac){   printf("%s [ip] [port]..\n",prac);}void * thread_run(void* arg){   printf("create a new thread \n");  int fd = (int)arg; while(1){  char buf[1024];  memset(buf,'\0',sizeof(buf));  ssize_t _s = read(fd,buf,sizeof(buf)-1);  if(_s >0){  buf[_s] = '\0';  printf("client : %s\n",buf);  write(fd,buf,strlen(buf));  }else if(_s == 0){   printf("client close..\n");      break;}else{  printf("read error...\n");       break;}  }    }int main(int argc,char* argv[]){if(argc != 3){guse(argv[0]);exit(0);}int sock = socket(AF_INET,SOCK_STREAM,0);if(sock < 0){perror("create socket error...");return 1;}struct sockaddr_in server_socket;struct sockaddr_in client_socket;bzero(&server_socket,sizeof(server_socket));server_socket.sin_family = AF_INET;server_socket.sin_addr.s_addr =htonl(atoi(argv[1]));server_socket.sin_port = htons(atoi(argv[2]));if(bind(sock,(struct sockaddr*)&server_socket,sizeof(server_socket))< 0){perror("bind error ...");return 2;}if(listen(sock,_BACKLOG_) < 0 ){perror("listen errno ...");close(sock); return 3;}printf("wait accept\n");while(1){socklen_t addrlen = sizeof(client_socket);int client_sock = accept(sock,(struct sockaddr*)&client_socket,&addrlen);if(client_sock < 0){perror("accept error...\n");close(sock);return 4;}printf("Yougot a connection from cient's ip is %s, prot is %d\n",inet_ntoa(client_socket.sin_addr),htons(client_socket.sin_port));pthread_t id;char buf_ip[1024];memset(buf_ip,'\0',sizeof(buf_ip));inet_ntop(AF_INET,&client_socket.sin_addr,buf_ip,sizeof(buf_ip));pthread_create(&id,NULL,thread_run,(void*)client_sock);pthread_detach(id);}return 0;}</span>


客户端:

<span style="font-size:18px;">#include<stdio.h>#include<sys/socket.h>#include<errno.h>#include<string.h>#include<sys/types.h>#include<unistd.h>#include<netinet/in.h>#include<arpa/inet.h>#include<stdlib.h>#define _BACKLOG_ 10void guse(const char* prac){printf("%s [ip] [port]..\n",prac);}int main(int argc,char* argv[]){if(argc != 3){guse(argv[0]);exit(0);}int sock = socket(AF_INET,SOCK_STREAM,0);if(sock < 0){perror("create socket error...");return 1;}struct sockaddr_in server_socket;bzero(&server_socket,sizeof(server_socket));server_socket.sin_family = AF_INET;//server_socket.sin_addr.s_addr =htonl(atoi(argv[1]));inet_pton(AF_INET,argv[1],&server_socket.sin_addr);server_socket.sin_port = htons(atoi(argv[2]));int ret = connect(sock,(struct sockaddr*)&server_socket,sizeof(server_socket));if(ret < 0){   perror("connect is failed..\n");  return 2;}printf("connect success \n");  char buf[1024];memset(buf,'\0',sizeof(buf));while(1){printf("client :..\n");//fflush(stdout);fgets(buf,sizeof(buf),stdin);buf[strlen(buf)-1] = '\0';write(sock,buf,strlen(buf));if(strncasecmp(buf,"quit",4) == 0){printf("quit\n");break;}printf("please wait...");read(sock,buf,sizeof(buf));printf("server :%s\n",buf);}close(sock);return 0;}</span>

来看看结果:


但是,这个程序是有BUG的,或者是有缺陷的,当你把服务器与客户端连通,CTRL+C结束服务器,然后再起服务器,这时你会看到:


这是怎么回事呢?

请大家看下面两幅图:



服务器端首先执行 LISTEN 原语进入被动打开状态( LISTEN ),等待客户端连接;
  当客户端的一个应用程序发出 CONNECT 命令后,本地的 TCP 实体为其创建一个连接记录并标记为 SYN SENT 状态,然后给服务器发送一个 SYN 报文段;
   服务器收到一个 SYN 报文段,其 TCP 实体给客户端发送确认 ACK 报文段同时发送一个 SYN 信号,进入 SYN RCVD 状态;
   客户端收到 SYN + ACK 报文段,其 TCP 实体给服务器端发送出三次握手的最后一个 ACK 报文段,并转换为 ESTABLISHED 状态;
    服务器端收到确认的 ACK 报文段,完成了三次握手,于是也进入 ESTABLISHED 状态。
  在此状态下,双方可以自由传输数据。当一个应用程序完成数据传输任务后,它需要关闭 TCP 连接。假设仍由客户端发起主动关闭连接。
·   客户端执行 CLOSE 原语,本地的 TCP 实体发送一个 FIN 报文段并等待响应的确认(进入状态 FIN WAIT 1 );
·    服务器收到一个 FIN 报文段,它确认客户端的请求发回一个 ACK 报文段,进入 CLOSE WAIT 状态;
·    客户端收到确认 ACK 报文段,就转移到 FIN WAIT 2 状态,此时连接在一个方向上就断开了;
·    服务器端应用得到通告后,也执行 CLOSE 原语关闭另一个方向的连接,其本地 TCP 实体向客户端发送一个 FIN 报文段,并进入 LAST ACK 状态,等待最后一个 ACK 确认报文段;
·    客户端收到 FIN 报文段并确认,进入 TIMED WAIT 状态,此时双方连接均已经断开,但 TCP 要等待一个 2 倍报文段最大生存时间 MSL ( Maximum Segment Lifetime ),确保该连接的所有分组全部消失,以防止出现确认丢失的情况。当定时器超时后, TCP 删除该连接记录,返回到初始状态( CLOSED )。
· 服务器收到最后一个确认 ACK 报文段,其 TCP 实体便释放该连接,并删除连接记录,返回到初始状态(CLOSED )。


MSL 是Maximum Segment Lifetime英文的缩写,中文可以译为“报文最大生存时间”,他是任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃。


2MSL即两倍的MSL,TCP的TIME_WAIT状态也称为2MSL等待状态,当TCP的一端发起主动关闭,在发出最后一个ACK包后,即第3次握 手完成后发送了第四次握手的ACK包后就进入了TIME_WAIT状态,必须在此状态上停留两倍的MSL时间,等待2MSL时间主要目的是怕最后一个 ACK包对方没收到,那么对方在超时后将重发第三次握手的FIN包,主动关闭端接到重发的FIN包后可以再发一个ACK应答包。在TIME_WAIT状态 时两端的端口不能使用,要等到2MSL时间结束才可继续使用。当连接处于2MSL等待阶段时任何迟到的报文段都将被丢弃。不过在实际应用中可以通过设置 SO_REUSEADDR选项达到不必等待2MSL时间结束再使用此端口。


 SO_REUSEADDR又该怎么设置呢?先看一个函数



<span style="font-size:18px;">int opt = 1;setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));</span>

这时,结果就是这样的:



本博文有许多不足,望多多指教。

0 0
原创粉丝点击