Unix网络通信socket之client

Unix下一个客户端至少要经历一下几个步骤:
1.socket()
函数原型:
int socket(int domain, int type, int protocol);

socket函数对应于普通文件的打开操作。普通文件的打开操作返回一个文件描述字,而socket()用于创建一个socket描述符 (socket descriptor),它唯一标识一个socket。这个socket描述字跟文件描述字一样,后续的操作都有用到它,把它作为参 数,通过它来进行一些读写操作。创建socket的时候,也可以指定不同的参数创建不同的socket描述符,

socket函数的三个参数 分别为:
domain:即协议域,又称为协议族(family)。

常用的协议族有,AF_INET、AF_INET6、AF_LOCAL(或称AF_UNIX, Unix域socket)、AF_ROUTE等等。协议族决定了socket的地址类型,在通信中必须采用对应的地址,如AF_INET决定了 要用ipv4地址(32位的)与端口号(16位的)的组合、AF_UNIX决定了要用一个绝对路径名作为地址。

type:指定socket类型。

常用的socket类型有,SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、 SOCK_SEQPACKET等等。

protocol:故名思意,就是指定协议。

常用的协议有,IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、 IPPROTO_TIPC等,它们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议,type和protocol并不 是可以随意组合的,如SOCK_STREAM不可以跟IPPROTO_UDP组合。当protocol为0时,会自动选择type类型对应的默认协议。

TCP建立连接需要三次握手:

在这里插入图片描述

1. Client首先向Server发送连接请求报文段,同步自己的seq(x),Client进入SYN_SENT状态。
2. Server收到Client的连接请求报文段,返回给Client自己的seq(y)以及ack(x+1),Server进入SYN_REVD状态。 
3. Client收到Server的返回确认,再次向服务器发送确认报文段ack(y+1),这个报文段已经可以携带数据了。Client进 入ESTABLISHED状态。 
4. Server再次收到Client的确认信息后,进入ESTABLISHED状态。

TCP连接至此建立起来了。为什么要做三次握手呢?握手的过程实际上是在通知对方自己的初始化序号(Initial Sequence Number),简称ISN,也就是上图中的x和y。x和y会被当作之后传输数据的一个依据,以保证TCP报文在传输过程中不会混乱。   我们回到TCP Header结构来看,Sequence Number和Acknowledgment Number都是占32位,所以seq和ack的取值范 围是0 ~ 232-1。seq和ack每增加到232-1,则重新从0开始。值得一提的是,seq的初始值(ISN)并不是每次都从0开始的。 我们设想一下,如果是从0开始,那么当TCP三次握手建立连接完成后,Client发送了30个报文,然后Client断线了。于是Client 重连,再次用0作为初始的seq,这样就会出现两个报文具有相同的seq,就出现了混乱。事实上TCP的做法是每隔4微秒就对ISN 做一次加1操作,当ISN到达2^32-1后再次从0开始的时候,已经过去了几个小时,之前的seq=0的报文已经不存在于这次连接中 了,这样就避免了上面的问题。

TCP断开连接需要四次握手:

在这里插入图片描述

(1)Client向Server发送断开连接请求的报文段,seq=m(m为Client后一次向Server发送报文段的后一个字节序号加1), Client进入FIN-WAIT-1状态。   
(2)Server收到断开报文段后,向Client发送确认报文段,seq=n(n为Server后一次向Client发送报文段的后一个字节序 号加1),ack=m+1,Server进入CLOSE-WAIT状态。此时这个TCP连接处于半开半闭状态,Server发送数据的话,Client仍然可 以接收到。   
(3)Server向Client发送断开确认报文段,seq=u(u为半开半闭状态下Server后一次向Client发送报文段的后一个字节序 号加1),ack=m+1,Server进入LAST-ACK状态。   
(4)Client收到Server的断开确认报文段后,向Server发送确认断开报文,seq=m+1,ack=u+1,Client进入TIME-WAIT状 态。   
(5)Server收到Client的确认断开报文,进入CLOSED状态,断开了TCP连接。   
(6)Client在TIME-WAIT状态等待一段时间(时间为2*MSL((Maximum Segment Life)),确认Client向Server发送的后一次 断开确认到达(如果没有到达,Server会重发步骤(3)中的断开确认报文段给Client,告诉Client你的后一次确认断开没有收 到)。如果Client在TIME-WAIT过程中没有再次收到Server的报文段,就进入CLOSES状态。TCP连接至此断开。      
TCP连接可靠性的体现:  
(1)TCP报文段的长度可变,根据收发双方的缓存状态、网络状态而调整。   
(2)当TCP收到发自TCP连接另一端的数据,它将发送一个确认。   
(3)当TCP发出一个段后,它启动一个定时器,等待目的端确认收到这个报文段,如果不能及时收到一个确认,将重发这个报 文段。   
(4)TCP将保持它首部和数据的检验和。如果通过检验和发现报文段有差错,这个报文段将被丢弃,等待超时重传。   (5)TCP将数据按字节排序,报文段中有序号,以确保顺序的正确性。  
(6)TCP还能提供流量控制。TCP连接的每一方都有收发缓存。TCP的接收端只允许另一端发送接收端缓冲区所能接纳的数 据。这将防止较快主机致使较慢主机的缓冲区溢出。   

需要注意的是,TCP报文传输采用接受后返回确认的方式来保证报文传输的可靠性,并不是意味着发送方在发送一个报文段 后就进入等待确认状态,让后面的报文段等着。也不是接收方在接收到一个报文后,对每一个报文都进行回复确认。   真实的情况是,对于发送方,在发送一个报文段后,复制一份该报文段的副本,然后继续进行下一个报文段的发送,如果没 有得到发送方的回复确认,就对该报文段进行超时重发。对于接收方来说,则采用“积累确认”的方式进行回复。接收者收到多
个连续的报文段后,只回复确认后一个报文段,表示在这之前的数据都已收到。以此达到提升传输效率的目的

因为TCP是可靠的安全的连接,所以个客户端都会经历三次握手的过程,最终得到与与服务器连接的,断开则需要需要四次握手之后才能断开。

实现代码:

#include <sys/types.h>      
#include <sys/socket.h>
#include <string.h>
#include<stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <errno.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <getopt.h>
#define	MSG_STR  "Hello network!"
#define	BUF_SIZE  1024
void print_usage(char *paogname)
{
    printf("%s usage : \n", paogname);
    printf("-i(--ipaddr): sepcify server IP address.\n");
    printf("-p(--port): specify sever port.\n");
    printf("-h(--help): print this help information.\n");

    return  ;

}
int main (int argc, char **argv)
{

    int                      sockfd;
    int                      rv = -1;
    struct sockaddr_in       servaddr;
    char                     *servip = NULL;
    int                      port    = 0;
    char                     buf[BUF_SIZE];
    int                      ch;
    struct option        opts[] = {         
        {"ipaddr", required_argument, NULL, 'i'},
        {"port", required_argument, NULL, 'p'},            
        {"help", no_argument, NULL, 'h'},          
        {NULL, 0, NULL, 0}

    };

    while((ch=getopt_long(argc, argv, "i:p:h", opts, NULL)) != -1 )    //基于Unix命令行参数解析
    {
        switch(ch)
        {
            case 'i':   
                servip=optarg;
                break;
            case 'p':
                port=atoi(optarg);
                break;
            case 'h':
                print_usage(argv[0]);
                return 0;
        }           
     }

    if( !servip || !port )        
    {
                        print_usage(argv[0]);
                        return 0;        
    } 
    sockfd = socket(AF_INET,SOCK_STREAM,0);   //客户端第一步  socket();
    if(sockfd < 0)
    {
        printf("Cearte socket failure :%s\a\n",strerror(errno));
        return -1;
    }
    printf("Create socket[%d] sucessfully.\n",sockfd);


    memset(&servaddr,0,sizeof(servaddr)); // bzero() 函数也OK bzero(&servaddr,sizeof(servaddr))
    servaddr.sin_family = AF_INET;         //指定协议簇 IPV4
    servaddr.sin_port = htons(port);       //host name to net(本地字节序转化为网络字节序(默认为大端字节序))
    inet_aton(servip,&servaddr.sin_addr);  //ASCII to net(本地字节序转化为网络字节序(默认为大端字节序))
    rv = connect(sockfd,(struct sockaddr *)&servaddr,sizeof(servaddr));  //客户端第二步  connect()

    if(rv <0)
    {
        printf("connect toserver[%s:%d] failure :%s\n\a",servip, port,strerror(errno));
        return -2;
    }
    printf("connect to server[%s:%d] successfully!\n",servip,port);


    while(1)
    {

        rv = write(sockfd,MSG_STR,sizeof(MSG_STR)); // //客户端第三步之write
        {
            printf("Write to server failure by socket[%d] failure: %s",sockfd,strerror(errno));
            return -3;
        }

        memset(buf,0,sizeof(buf));
        rv = read(sockfd,buf,sizeof(buf)); // //客户端第三步之read();  
        if(rv < 0)
        {
            printf("Read to server socket[%d] failure: %s",sockfd,strerror(errno));
            return -4;
            continue;
        }
        else if(rv == 0)
        {
        
            printf("socket[%d] get disconnected\n",sockfd);
            return -5;
            continue;
        }
        else if(rv >0)
        {
            printf("Read %d bytes data form Server: %s\n",rv,buf);
            break;
        }

    }


    close(sockfd);

    return 0;
} 

发布了16 篇原创文章 · 获赞 9 · 访问量 802

猜你喜欢

转载自blog.csdn.net/qq_44045338/article/details/103544258