tcp三次握手、socket编程

建立TCP需要三次握手才能建立,而断开连接则需要四次握手。整个过程如下图所示:

tcp三次握手、socket编程_第1张图片

先来看看如何建立连接的。

tcp三次握手、socket编程_第2张图片

首先Client端发送连接请求报文,Server段接受连接后回复ACK报文,并为这次连接分配资源。Client端接收到ACK报文后也向Server段发生ACK报文,并分配资源,这样TCP连接就建立了。

那如何断开连接呢?简单的过程如下:

tcp三次握手、socket编程_第3张图片

【注意】中断连接端可以是Client端,也可以是Server端。

假设Client端发起中断连接请求,也就是发送FIN报文。Server端接到FIN报文后,意思是说"我Client端没有数据要发给你了",但是如果你还有数据没有发送完成,则不必急着关闭Socket,可以继续发送数据。所以你先发送ACK,"告诉Client端,你的请求我收到了,但是我还没准备好,请继续你等我的消息"。这个时候Client端就进入FIN_WAIT状态,继续等待Server端的FIN报文。当Server端确定数据已发送完成,则向Client端发送FIN报文,"告诉Client端,好了,我这边数据发完了,准备好关闭连接了"。Client端收到FIN报文后,"就知道可以关闭连接了,但是他还是不相信网络,怕Server端不知道要关闭,所以发送ACK后进入TIME_WAIT状态,如果Server端没有收到ACK则可以重传。“,Server端收到ACK后,"就知道可以断开连接了"。Client端等待了2MSL后依然没有收到回复,则证明Server端已正常关闭,那好,我Client端也可以关闭连接了。Ok,TCP连接就这样关闭了!

整个过程Client端所经历的状态如下:

tcp三次握手、socket编程_第4张图片

而Server端所经历的过程如下:转载请注明:blog.csdn.net/whuslei

tcp三次握手、socket编程_第5张图片

【注意】 在TIME_WAIT状态中,如果TCP client端最后一次发送的ACK丢失了,它将重新发送。TIME_WAIT状态中所需要的时间是依赖于实现方法的。典型的值为30秒、1分钟和2分钟。等待之后连接正式关闭,并且所有的资源(包括端口号)都被释放。

【问题1】为什么连接的时候是三次握手,关闭的时候却是四次握手?
答:因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当Server端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉Client端,"你发的FIN报文我收到了"。只有等到我Server端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四步握手。

【问题2】为什么TIME_WAIT状态需要经过2MSL(最大报文段生存时间)才能返回到CLOSE状态?

答:虽然按道理,四个报文都发送完毕,我们可以直接进入CLOSE状态了,但是我们必须假象网络是不可靠的,有可以最后一个ACK丢失。所以TIME_WAIT状态就是用来重发可能丢失的ACK报文。



socket编程例子:

<tcp server>:

#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <memory.h>
#include <unistd.h>
//#include <linux/in.h>
#include <netinet/in.h>
//#include <linux/inet_diag.h>
#include <arpa/inet.h>

#include <signal.h>

/**
  关于 sockaddr  sockaddr_in  socketaddr_un说明
  http://maomaozaoyue.blog.sohu.com/197538359.html
  */

#define PORT    11910   //定义通信端口
#define BACKLOG 5       //定义侦听队列长度
#define buflen  1024

void process_conn_server(int s);
void sig_pipe(int signo);

int ss,sc;  //ss为服务器socket描述符,sc为某一客户端通信socket描述符

int main(int argc,char *argv[])
{

    struct sockaddr_in server_addr; //存储服务器端socket地址结构
    struct sockaddr_in client_addr; //存储客户端 socket地址结构

    int err;    //返回值
    pid_t pid;  //分叉进行的ID

    /*****************socket()***************/
    ss = socket(AF_INET,SOCK_STREAM,0); //建立一个序列化的,可靠的,双向连接的的字节流
    if(ss<0)
    {
        printf("server : server socket create error\n");
        return -1;
    }
    //注册信号
    sighandler_t ret;
    ret = signal(SIGTSTP,sig_pipe);
    if(SIG_ERR == ret)
    {
        printf("信号挂接失败\n");
        return -1;
    }
    else
        printf("信号挂接成功\n");


    /******************bind()****************/
    //初始化地址结构
    memset(&server_addr,0,sizeof(server_addr));
    server_addr.sin_family = AF_INET;           //协议族
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);   //本地地址
    server_addr.sin_port = htons(PORT);

    err = bind(ss,(struct sockaddr *)&server_addr,sizeof(sockaddr));
    if(err<0)
    {
        printf("server : bind error\n");
        return -1;
    }

    /*****************listen()***************/
    err = listen(ss,BACKLOG);   //设置监听的队列大小
    if(err < 0)
    {
        printf("server : listen error\n");
        return -1;
    }

    /****************accept()***************/
    /**
    为类方便处理,我们使用两个进程分别管理两个处理:
    1,服务器监听新的连接请求;2,以建立连接的C/S实现通信
    这两个任务分别放在两个进程中处理,为了防止失误操作
    在一个进程中关闭 侦听套接字描述符 另一进程中关闭
    客户端连接套接字描述符。注只有当所有套接字全都关闭时
    当前连接才能关闭,fork调用的时候父进程与子进程有相同的
    套接字,总共两套,两套都关闭掉才能关闭这个套接字
    */

    for(;;)
    {
        socklen_t addrlen = sizeof(client_addr);
        //accept返回客户端套接字描述符
        sc = accept(ss,(struct sockaddr *)&client_addr,&addrlen);  //注,此处为了获取返回值使用 指针做参数
        if(sc < 0)  //出错
        {
            continue;   //结束此次循环
        }
        else
        {
            printf("server : connected\n");
        }

        //创建一个子线程,用于与客户端通信
        pid = fork();
        //fork 调用说明:子进程返回 0 ;父进程返回子进程 ID
        if(pid == 0)        //子进程,与客户端通信
        {
            close(ss);
            process_conn_server(sc);
        }
        else
        {
            close(sc);
        }
    }
}

/**
  服务器对客户端连接处理过程;先读取从客户端发送来的数据,
  然后将接收到的数据的字节的个数发送到客户端
  */

//通过套接字 s 与客户端进行通信
void process_conn_server(int s)
{
    ssize_t size = 0;
    char buffer[buflen];  //定义数据缓冲区
    for(;;)
    {
        //等待读
        for(size = 0;size == 0 ;size = read(s,buffer,buflen));
        //输出从客户端接收到的数据
        printf("%s",buffer);

        //结束处理
        if(strcmp(buffer,"quit") == 0)
        {
            close(s);   //成功返回0,失败返回-1
            return ;
        }
        sprintf(buffer,"%d bytes altogether\n",size);
        write(s,buffer,strlen(buffer)+1);
    }
}
void sig_pipe(int signo)
{
    printf("catch a signal\n");
    if(signo == SIGTSTP)
    {
        printf("接收到 SIGTSTP 信号\n");
        int ret1 = close(ss);
        int ret2 = close(sc);
        int ret = ret1>ret2?ret1:ret2;
        if(ret == 0)
            printf("成功 : 关闭套接字\n");
        else if(ret ==-1 )
            printf("失败 : 未关闭套接字\n");

        exit(1);
    }
}

<tcp client>:

#include <stdio.h>
#include <strings.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
//#include <linux/in.h>
#include <stdlib.h>
#include <memory.h>
#include <arpa/inet.h>
#include <netinet/in.h>

#include <signal.h> //添加信号处理  防止向已断开的连接通信

/**
  信号处理顺序说明:在Linux操作系统中某些状况发生时,系统会向相关进程发送信号,
  信号处理方式是:1,系统首先调用用户在进程中注册的函数,2,然后调用系统的默认
  响应方式,此处我们可以注册自己的信号处理函数,在连接断开时执行
  */


#define PORT    11910
#define Buflen  1024

void process_conn_client(int s);
void sig_pipe(int signo);    //用户注册的信号函数,接收的是信号值

int s;  //全局变量 , 存储套接字描述符

int main(int argc,char *argv[])
{

    sockaddr_in server_addr;
    int err;
    sighandler_t ret;
    char server_ip[50] = "";
    /********************socket()*********************/
    s= socket(AF_INET,SOCK_STREAM,0);
    if(s<0)
    {
        printf("client : create socket error\n");
        return 1;
    }

    //信号处理函数  SIGINT 是当用户按一个 Ctrl-C 建时发送的信号
    ret = signal(SIGTSTP,sig_pipe);
    if(SIG_ERR == ret)
    {
        printf("信号挂接失败\n");
        return -1;
    }
    else
        printf("信号挂接成功\n") ;


    /*******************connect()*********************/
    //设置服务器地址结构,准备连接到服务器
    memset(&server_addr,0,sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(PORT);
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);

    /*将用户数入对额字符串类型的IP格式转化为整型数据*/
    //inet_pton(AF_INET,argv[1],&server_addr.sin_addr.s_addr);
    printf("please input server ip address : \n");
    read(0,server_ip,50);
    //err = inet_pton(AF_INET,server_ip,&server_addr.sin_addr.s_addr);
    server_addr.sin_addr.s_addr = inet_addr(server_ip);

    err = connect(s,(struct sockaddr *)&server_addr,sizeof(sockaddr));
    if(err == 0)
    {
        printf("client : connect to server\n");
    }
    else
    {
        printf("client : connect error\n");
        return -1;
    }
    //与服务器端进行通信
    process_conn_client(s);
    close(s);

}
void process_conn_client(int s)
{

    ssize_t size = 0;
    char buffer[Buflen];

    for(;;)
    {
        memset(buffer,'\0',Buflen);
        /*从标准输入中读取数据放到缓冲区buffer中*/
        size = read(0,buffer,Buflen);   // 0,被默认的分配到标准输入  1,标准输出  2,error
        if(size >  0)
        {
            //当向服务器发送 “quit” 命令时,服务器首先断开连接
            write(s,buffer,strlen(buffer)+1);   //向服务器端写

            //等待读取到数据
            for(size = 0 ; size == 0 ; size = read(s,buffer,Buflen) );

            write(1,buffer,strlen(buffer)+1);   //向标准输出写
        }
    }
}

void sig_pipe(int signo)    //传入套接字描述符
{
    printf("Catch a signal\n");
    if(signo == SIGTSTP)
    {

        printf("接收到 SIGTSTP 信号\n");
        int ret = close(s);
        if(ret == 0)
            printf("成功 : 关闭套接字\n");
        else if(ret ==-1 )
            printf("失败 : 未关闭套接字\n");
        exit(1);
    }
}


<span style="font-family:Microsoft YaHei;">运行结果:
</span><p>TCPServer端</p><p><img src="http://img.my.csdn.net/uploads/201210/07/1349582726_2335.png" alt="" />
</p><p>TCPClient端</p><p><img src="http://img.my.csdn.net/uploads/201210/07/1349582730_9466.png" alt="" />
</p><p>netstat查看网络连接</p><img src="http://img.my.csdn.net/uploads/201210/07/1349579379_9956.png" alt="" />

<udp server>:

/************************************************************************* 
 * NAME    :udp_server_xss.c 
 * FUNCTION:接收客户端发送过来的文件并保存
 * INPUT   :
 * AUTHOR  : Alan Xu 
 * BUG     :当传送的文件字节数是BUFFER_SIZE的整数倍时,少输出BUFFER_SIZE个字节
 ************************************************************************/
#include<sys/types.h> 
#include<sys/socket.h> 
#include<unistd.h> 
#include<netinet/in.h> 
#include<arpa/inet.h> 
#include<stdio.h> 
#include<stdlib.h> 
#include<errno.h> 
#include<netdb.h> 
#include<stdarg.h> 
#include<string.h> 
  
#define SERVER_PORT 8000 
#define BUFFER_SIZE 1024 
#define FILE_NAME_MAX_SIZE 512 
  
/* 包头 */
typedef struct
{ 
    int id; 
    int buf_size; 
}PackInfo; 
  
/* 接收包 */
struct SendPack 
{ 
    PackInfo head; 
    char buf[BUFFER_SIZE]; 
} data; 

int main() 
{ 
     int id = 1; 
  
    /* 创建UDP套接口 */
    struct sockaddr_in server_addr; 
    bzero(&server_addr, sizeof(server_addr)); 
    server_addr.sin_family = AF_INET; 
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY); 
    server_addr.sin_port = htons(SERVER_PORT); 
  
    /* 创建socket */
    int server_socket_fd = socket(AF_INET, SOCK_DGRAM, 0); 
    if(server_socket_fd == -1) 
    { 
        perror("Create Socket Failed:"); 
        exit(1); 
    } 
    
    /* 绑定套接口 */
    if(-1 == (bind(server_socket_fd,(struct sockaddr*)&server_addr,sizeof(server_addr)))) 
    { 
        perror("Server Bind Failed:"); 
        exit(1); 
    } 
  
    while(1) 
    {   
        /* 定义一个地址,用于捕获客户端地址 */
        struct sockaddr_in client_addr; 
        socklen_t client_addr_length = sizeof(client_addr); 
  
        /* 存放接收数据 */
        char buffer[BUFFER_SIZE]; 
        bzero(buffer, BUFFER_SIZE); 

        if(recvfrom(server_socket_fd, buffer, BUFFER_SIZE,0,(struct sockaddr*)&client_addr, &client_addr_length) == -1) 
        { 
            perror("Receive Data Failed:"); 
            exit(1); 
        } 
  
        /* 从buffer中拷贝出file_name */
        char file_name[FILE_NAME_MAX_SIZE+1]; 
        bzero(file_name,FILE_NAME_MAX_SIZE+1);
        strncpy(file_name, buffer, strlen(buffer)>FILE_NAME_MAX_SIZE?FILE_NAME_MAX_SIZE:strlen(buffer)); 
  
        FILE *fp = fopen(file_name, "w+"); 

	/* 从客户端接收数据,并写入文件 */
        int len = 1; 
        while(len > 0) 
        { 
             PackInfo pack_info; 
	     memset(&pack_info,0,sizeof(pack_info));    //此处第三个参数不能用strlen();
             len = recvfrom(server_socket_fd, (char*)&data, sizeof(data), 0, (struct sockaddr*)&client_addr,&client_addr_length); 
	     if(strcmp(data.buf,"END") == 0)  //判断是否是结束符
		     break;
	     if(len > 0)
             { 
	    
                  if(data.head.id == id) //第一个字节
                  { 
                       pack_info.id = data.head.id; 
                       pack_info.buf_size = data.head.buf_size; 
                       ++id; 
                       /* 发送数据包确认信息 */
                       if(sendto(server_socket_fd, (char*)&pack_info, sizeof(pack_info), 0, (struct sockaddr*)&client_addr, client_addr_length) < 0) 
                       { 
                             printf("Send confirm information failed!"); 
                       } 
                       /* 写入文件 */
                       if(fwrite(data.buf, sizeof(char), data.head.buf_size, fp) < data.head.buf_size) 
                       { 
                             printf("File:\t%s Write Failed\n", file_name); 
                             break; 
                       } 
		       //printf("data.head.buf_size = %d\n ",data.head.buf_size);
	               //printf(" id = %d\n",id);
                  } 
                  else if(data.head.id < id) // 如果是重发的包 
                  { 
                       pack_info.id = data.head.id; 
                       pack_info.buf_size = data.head.buf_size; 
                       /*重发数据包确认信息*/
                       if(sendto(server_socket_fd, (char*)&pack_info, sizeof(pack_info), 0, (struct sockaddr*)&client_addr, client_addr_length) < 0) 
                       { 
                             printf("Send confirm information failed!"); 
                       } 
                  } 
                  else
                  {  
		        break;
                  } 
            } 
	    /*if(pack_info.buf_size < BUFFER_SIZE)
	    {
                   printf("the buf_size = %d\n",pack_info.buf_size);
		   break;
	    }*/
      } 
      printf("Receive File success \n"); 
      fclose(fp);
  } 
  close(server_socket_fd); 
  return 0; 
} 

<udp client>:

/************************************************************************* 
 * NAME    :udp_client_xss.c 
 * FUNCTION:发送待发送的文件名给服务器端,然后发送文件
 * INPUT   :<filename>
 * AUTHOR  : Alan Xu 
 ************************************************************************/
#include<sys/types.h> 
#include<sys/socket.h> 
#include<unistd.h> 
#include<netinet/in.h> 
#include<arpa/inet.h> 
#include<stdio.h> 
#include<stdlib.h> 
#include<errno.h> 
#include<netdb.h> 
#include<stdarg.h> 
#include<string.h> 
  
#define SERVER_PORT 8000 
#define BUFFER_SIZE 1024 
#define FILE_NAME_MAX_SIZE 512 
  
/* 包头 */
typedef struct 
{ 
  int id; 
  int buf_size; 
}PackInfo; 
  
/* 接收包 */
struct RecvPack 
{ 
  PackInfo head; 
  char buf[BUFFER_SIZE]; 
} data; 


char* transdir(char *t,char *s)
{
    int len,len_temp;
    int n = 0;
    len = strlen(s);
    len_temp = len;
    while(s[len_temp-1] != '/')
    {
        len_temp--;
        n++;
	if(len == n)
		break;
    }
    if(len == n)
	 strcat(t,s);
    else
         strcat(t,s+len-n);
    return t;
}

  
int main() 
{ 
    int send_id = 0; //发送id 
    int receive_id = 0; // 接收id

    /* 服务端地址 */
    struct sockaddr_in server_addr; 
    bzero(&server_addr, sizeof(server_addr)); 
    server_addr.sin_family = AF_INET; 
    server_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); 
    server_addr.sin_port = htons(SERVER_PORT); 
    socklen_t server_addr_length = sizeof(server_addr); 
  
    /* 创建socket */
    int client_socket_fd = socket(AF_INET, SOCK_DGRAM, 0); 
    if(client_socket_fd < 0) 
    { 
        perror("Create Socket Failed:"); 
        exit(1); 
    } 
  
    /* 输入文件名到缓冲区 */
    char file_name[FILE_NAME_MAX_SIZE+1]; 
    bzero(file_name, FILE_NAME_MAX_SIZE+1); 
    printf("Please Input File Name On Client: "); 
    scanf("%s", file_name); 
    
    char file_name_temp[FILE_NAME_MAX_SIZE+1]; 
    bzero(file_name_temp, FILE_NAME_MAX_SIZE+1);

    transdir(file_name_temp,file_name);
    printf("the file_name_temp is %s\n",file_name_temp);

    char buffer[BUFFER_SIZE]; 
    bzero(buffer, BUFFER_SIZE); 
    strncpy(buffer, file_name_temp, strlen(file_name_temp)>BUFFER_SIZE?BUFFER_SIZE:strlen(file_name_temp)); 
  
    /* 发送文件名 */
    if(sendto(client_socket_fd, buffer, BUFFER_SIZE,0,(struct sockaddr*)&server_addr,server_addr_length) < 0) 
    { 
        perror("Send File Name Failed:"); 
        exit(1); 
    } 

    /* 打开文件,准备写入 */
    FILE *fp = fopen(file_name, "r"); 
    if(NULL == fp) 
    { 
        printf("File:%s Not Found.\n", file_name);  
    } 
    
    int len = 0; 
     /* 每读取一段数据,便将其发给客户端 */
    while(1) 
    { 
         PackInfo pack_info; 
         memset(&pack_info,0,sizeof(pack_info));
         if(receive_id == send_id) 
         { 
              ++send_id; 
              if((len = fread(data.buf, sizeof(char), BUFFER_SIZE, fp)) > 0) 
              { 
                    data.head.id = send_id; //发送id放进包头,用于标记顺序 
                    data.head.buf_size = len; // 记录数据长度 

		    //printf("send_id = %d\n",send_id);

                    if(sendto(client_socket_fd, (char*)&data, sizeof(data), 0, (struct sockaddr*)&server_addr, server_addr_length) < 0) 
                    { 
                          perror("Send File Failed:"); 
                          break; 
                    } 
                    /* 接收确认消息 */
                    recvfrom(client_socket_fd, (char*)&pack_info, sizeof(pack_info), 0, (struct sockaddr*)&server_addr, &server_addr_length); 
                    receive_id = pack_info.id;  
               } 
               else
               { 
                    break; 
               } 
          } 
          else
          { 
               // 如果接收的id和发送的id不相同,重新发送 
               if(sendto(client_socket_fd, (char*)&data, sizeof(data), 0, (struct sockaddr*)&server_addr, server_addr_length) < 0) 
               { 
                     perror("Send File Failed:"); 
                     break; 
               } 
               /*接收确认消息 */
               recvfrom(client_socket_fd, (char*)&pack_info, sizeof(pack_info), 0, (struct sockaddr*)&server_addr, &server_addr_length); 
               receive_id = pack_info.id;  
          } 
     } 
     sendto(client_socket_fd, "END", 3, 0, (struct sockaddr*)&server_addr, server_addr_length);//发送结束符
     printf("send File:%s success\n", file_name); 
     fclose(fp); 
     close(client_socket_fd); 
     return 0; 
}
以上的<span style="font-family:Microsoft YaHei;">的socket实现文件传输功能。</span>







你可能感兴趣的:(linux,tcp,socket,C语言,UDP)