TCP网络编程

2020-05-07  本文已影响0人  潘雪雯

socket介绍

网络层的IP地址可以唯一标识网络中的主机,而传输层的“协议+端口”可以唯一标识主机中的应用程序(进程)。这样利用三元组(IP地址,协议,端口)就可以标识网络的进程。
使用TCP/IP协议的应用程序通常采用应用编程接口:套接字(socket),采用open-->write/read-->close模式实现。


TCP交互流程
  1. 服务器根据地址类型(ipv4,ipv6)、socket类型,协议创建socket。
  2. 服务器为socket绑定IP地址和端口号
  3. 服务器socket监听端口号请求,随时准备接收客户端发来的连接,这时候服务器的socket并没有被打开
  4. 客户端创建socket
  5. 客户端打开socket,根据服务器IP地址和端口号试图连接服务器socket
  6. 服务器socket接收到客户端socket请求,被动打开,开始接收客户端请求,直到客户端返回连接信息。这时socket进入阻塞状态,即accept()方法一直到客户端返回连接信息后才返回,开始接收下一个客户端连接请求。
  7. 客户端连接成功,向服务器发送连接状态信息
  8. 服务器accept方法发挥,连接成功
  9. 客户端向socket写入信息
  10. 服务器读取信息
  11. 客户端关闭
  12. 服务器端关闭

socket接口函数

  1. domain:即协议域,又称协议族(family),如AF_INETAF_INET6AF_LOCAL。决定了socket的地址类型,在通信中必须采用对应的地址。如AF_INET决定了要用ipv4地址(32位)与端口号(16位)的组合。
  2. type: 指定socket类型。常见的socket类型有:SOCK_STREAM表示提供面向连接的稳定数据传输、SOCK_DGRAM表示使用不连续、不可靠的数据包连接。
  3. protocal:指定协议。常用的协议有IPPROTO_TCP对应TCP传输协议、IPPROTO_UDP对应UDP传输协议、IPPROTO_SCTP对应STCP传输协议、IPPROTO_TIPC对应TIPC传输协议。
    若protocal为0 ,会自动选择type类型对应的默认协议。
int socket(AF_INET, SOCK_STREAM, 0)

4)函数返回值:若失败就会返回-1,成功会返回新创建的套接字的描述符。为整数类型的值。

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

函数的3个参数分别如下描述:

  1. sockfd:即socket描述字,是通过socket()函数创建来唯一标识一个socket的,bind()函数就是将给这个描述字绑定一个名字。
  2. addr: 是一个const struct sockaddr 指针,指向要绑定给sockfd的协议地址。这个地址结构根据地址创建socket 时的地址协议族的不同而不同。是一个结构体类型指针。
    其中
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY)中参数INADDR_ANY用于多网卡的机器上,多个IP都可以连上。若设置为servaddr.sin_addr.s_addr = inet_addr("192.168.1.1")*则表示只能连接到192.168.1.1。
    image.png
  3. addrlen:对应的是地址的长度。
  4. 函数返回值:若函数执行成功,返回值为0,反之为SOCKET_ERROR
int listen(int sockfd, int backlog);
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

listen函数的第一个参数即为要监听的socket描述字,第二个参数为相应socket可以排队的最大连接个数。socket()函数创建的socket默认是一个主动类型的,listen函数将socket变为被动类型的,等待客户的连接请求。
connect函数的第一个参数即为要监听的socket描述字,第二个参数为服务器的socket地址,第三个参数为socket地址的长度。客户端通过调用
connect函数建立于TCP服务器的连接。

int accept(int sockfd, const struct sockaddr *addr, socklen_t *addrlen)

accept函数的第一个参数即为服务器的socket描述字,第二个参数为struct sockaddr *的指针,用于返回客户端的协议地址,第三个参数为协议地址的长度。若accept成功,则其返回值由内核自动生成一个全新的描述符,代表与返回客户的TCP连接。

  1. read和write函数
    网络I/O操作常用的是read()和write()。read()的函数原型是:
ssize_t read(int fd,void *buf,size_t count);

read()函数负责从fd中读取内容。当读取成功时,read()返回实际所读的字节数,返回值0表示已经读到文件的结束,小于0表示出现错误,若错误为EINRT表示在读的时候出现中断错误。。3个参数分别是:1)fd表示socket描述字 2) buf表示缓冲区 3) count表示缓冲区长度。
write()的函数原型是:

ssize_t write(int fd,void *buf,size_t count);

writed()函数将buf中的nbytes字节内容写入文件描述符fd成功时返回写的字节数,失败时返回-1,并设置errno变量。返回值大于0,表示写了部分或全部数据;返回值小于0表示出现错误,若错误为EINRT表示在写的时候出现中断错误。

代码

  1. 创建socket,并给serveraddress赋值
    if( (listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1 ){
        printf("create socket error: %s(errno: %d)\n",strerror(errno),errno);
        return 0;
    }

    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 使用自己的 ip 地址
    servaddr.sin_port = htons(1101);
  1. 绑定socket和端口号
    if( bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1){
        printf("bind socket error: %s(errno: %d)\n",strerror(errno),errno);
        return 0;
    }
  1. 监听端口号
if( listen(listenfd, 10) == -1){
        printf("listen socket error: %s(errno: %d)\n",strerror(errno),errno);
        return 0;
    }

<- -----------------------服务器的准备工作完成--------------------------------- ->

  1. 创建socket
if( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0){
        printf("create socket error: %s(errno: %d)\n", strerror(errno),errno);
        return 0;
    }

    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(1101);
  1. 连接指定计算机端口
if( connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0){
        printf("connect error: %s(errno: %d)\n",strerror(errno),errno);
        return 0;
    }
  1. 发送数据给服务器或向socket写入数据
printf("send msg to server: \n");
    fgets(sendline, 4096, stdin);
    if( send(sockfd, sendline, strlen(sendline), 0) < 0){
        printf("send msg error: %s(errno: %d)\n", strerror(errno), errno);
        return 0;
    }
    close(sockfd);
  1. 服务器接收来自客户端的连接请求从socket中读取字符accept()
printf("======waiting for client's request======\n");
    while(1){
        if( (connfd = accept(listenfd, (struct sockaddr*)NULL, NULL)) == -1){
            printf("accept socket error: %s(errno: %d)",strerror(errno),errno);
            continue;
        }
  1. 服务器从socket中读取数据recv()
        n = recv(connfd, buff, MAXLINE, 0);
        buff[n] = '\0';
        printf("recv msg from client: %s\n", buff);
        close(connfd);
    }
    close(listenfd);
image.png
如果两次连接的间隔太短不到两个MSL等待时间(MSL指一个片段在网络中最大的存货时间),也就是四次挥手没有完成没有正常关闭,会出现bind socket error: Address already in use(errno: 98)
上一篇下一篇

猜你喜欢

热点阅读