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

<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_ 10

static 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_ 10

void 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
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值