文章

TCP介绍

TCP介绍

TCP服务端

实现流程

Socket通信服务端

socket函数

int socket(int domain, int type, int protocol);:创建一个socket文件描述符fd

  • domain:输入参数,协议域、地址域或协议族
  • type:输入参数,socket类型;
  • protocol:输入参数,指定使用的协议;
  • 返回值 :成功返回一个非负整数的fd,失败则返回-1,并设置errno
  • 头文件 : <sys/socket.h>

常用 domain:AF_INETAF_INET6AF_LOCAL(或称AF_UNIX)、AF_ROUTE

常用 type:SOCK_STREAMSOCK_DGRAMSOCK_RAWSOCK_PACKETSOCK_SEQPACKET等等

常用 protocol:IPPROTO_TCPIPPTOTO_UDPIPPROTO_SCTPIPPROTO_TIPC等等。如果该参数为0,则让系统自动选择合适的协议,一般我们也这么操作

bind函数

分配了一个Socket描述符后,服务端的第二步就是使用在这个描述符用Bind绑定。

Bind()系统调用的主要用处:

  1. 服务器向系统注册它的众所周知的地址。面向连接和无连接的服务器在接受客户的请求之前都必须做这一步。 

  2. 客户可为自己注册一个特定的地址,以便服务器可以用这个有效的地址送回响应。

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);:绑定一个固定的socket地址

  • sockfd:输入参数,socket文件描述符,即socket()的返回值
  • addr:输入参数,指向sock地址的指针
  • addrlen:输入参数,sock地址的长度
  • 返回值:成功返回0,失败返回-1,并设置errno

listen函数

int listen(int sockfd, int backlog);:监听socket套接字是否有客户端连接它

  • sockfd:输入参数,被监听的套接字
  • backlog:输入参数,最大等待队列数量,宏SOMAXCONN为系统设定的最大值,可通过/etc/sysctl.conf修改,是在Server程序调用accept函数之前最大允许进入的连接请求数,多余的连接请求将被拒绝
  • 返回值:成功返回0,失败返回-1,并设置errno

accept函数

在listen监听到有新客户端时,就可以用accept函数响应客户的连接请求,建立与客户端的连接。产生一个新的socket描述符来描述该连接,这个连接用来与发起该连接请求的客户交换数据。

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);:接受socket连接请求

  • sockfd:输入参数,被监听的套接字
  • addr:输出参数,返回客户端的地址,可为NULL
  • addrlen:输入参数,返回addr客户端的地址的长度,可为NULL
  • 返回值:成功返回已连接的新套接字描述符connfd,失败返回-1,并设置errno

recv函数

int recv(int sockfd, void *buf, int len, int flags);:从socket接收数据

  • sockfd:输入参数,从这个socket接收数据
  • buf:输出参数,用来保存接收到的数据
  • len:输入参数,指定buf的长度,表示最多接收这么多个字节的数据
  • flags:输入参数,flags,指定对应的选项,一般置为0,但可以选择下列设置:
    • MSG_DONTROUTE:表示不使用指定路由,对send、sendto有效 
    • MSG_PEEK:对recv, recvfrom有效,表示读出网络数据后不清除已读的数据 
    • MSG_OOB:对发送接收都有效,表示发送或接受加急数据
  • 返回值:成功返回接收到的数据大小,返回0表示对方不再发送数据(可以理解为关闭了连接),出错返回-1,并设置errno

send函数

int send(int sockfd, void *buf, int len, int flags);:向socket发送数据

  • sockfd:输入参数,向这个socket发送数据
  • buf:输入参数,发送buf指向的数据
  • len:输入参数,指定buf的长度,指定要发送的数据大小,为要发送的数据的实际字节数+1
  • flags:输入参数,flags,指定对应的选项,一般置为0,但可以选择下列设置:
    • MSG_DONTROUTE:表示不使用指定路由,对send、sendto有效 
    • MSG_PEEK:对recv, recvfrom有效,表示读出网络数据后不清除已读的数据 
    • MSG_OOB:对发送接收都有效,表示发送或接受加急数据
  • 返回值:成功返回已发送的数据大小,失败则返回-1,并设置errno

recvfrom函数

int recvfrom(int sockfd, void *buf, int len, int flags, struct sockaddr *addr, socklen_t *addrlen);:从udp socket接收数据

  • sockfd:输入参数,从该socket接收数据
  • buf:输出参数,将接收的数据存放在buf上
  • len:输入参数,指定buf的长度
  • flags:输入参数,flags,指定对应的选项,一般置为0
  • addr:输出参数,保存该数据的发送方地址
  • addrlen:输入参数,指定发送方地址的长度
  • 返回值:成功返回接收到的数据大小,失败则返回-1,并设置errno

sendto函数

int sendto(int sockfd, void *buf, int len, int flags, struct sockaddr *addr, socklen_t addrlen);:向udp socket发送数据

  • sockfd:输入参数,向该socket发送数据
  • buf:输入参数,发送buf指向的数据
  • len:输入参数,指定buf的长度
  • flags:输入参数,flags,指定对应的选项,一般置为0
  • addr:输入参数,指定接收方的地址
  • addrlen:输入参数,指定接收方的地址的长度
  • 返回值:成功返回发送的数据大小,失败则返回-1,并设置errno

最后一个参数flags

  • MSG_WAITALL:用于recv,尽可能等待所有数据
  • MSG_DONTWAIT:用于recv、send,仅本次操作不阻塞
  • MSG_DONTROUTE:用于send,绕过路由表查找
  • MSG_OOB:用于recv、send,发送或接收带外数据
  • MSG_PEEK:用于recv,窥看外来数据,不将其从socket缓冲区中删除

shutdown函数

int shutdown(int sockfd, int howto);:关闭tcp连接

  • sockfd:输入参数,即将关闭连接的套接字
  • howto:输入参数,定义如何关闭:SHUT_RD值为0,关闭读、SHUT_WR值为1,关闭写、SHUT_RDWR值为2,关闭读写
  • 返回值:成功返回0,失败返回-1,并设置errno

close函数

int close(int fd);:关闭socket

  • fd:输入参数,要关闭的文件描述符fd
  • 返回值:成功返回0,失败返回-1,并设置errno

shutdown和close区别

shutdown()函数用于tcp连接的socket套接字,对udp无效

用于更精细的控制流的读和写,可以只关闭读,也可以只关闭写,或者读写都关闭,但是关闭后,该sockfd依旧是有效的,仍需调用close进行关闭

close()函数用于关闭对应的fd文件描述符,socket也是特殊的文件,注意,close只是将对应的sockfd的引用计数减1,当引用计数减到0时,系统才关闭该sockfd

还有一点:在多进程程序中,使用shutdown会导致共享进程的连接也被关闭,读写出现错误,而close不会影响共享进程的socket

程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

#define LISTEN_PORT 4160
#define BUF_SIZE 128

int main() {
    int listenfd;

    printf("Socket Server start ...\n");

    // 1. Create socket
    if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        perror("Socket create failed");
        return -1;
    }

    // 2. Bind socket
    struct sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(server_addr));

    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);    // 32位无符号整数从主机字节顺序转成网络字节顺序
    server_addr.sin_port = htons(LISTEN_PORT);        // 16位无符号短整数(端口号)从主机字节顺序转成网络字节顺序

    int error;

    // error = bind(listenfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
    if (bind(listenfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0){
        perror("bind_listenfd  error");
        return -1;
    }


    // 3. Listen
    if (listen(listenfd, SOMAXCONN) < 0) {
        perror("listen_listenfd error");
        return -1;
    }
    
    // 4. Accept
    int connfd;
    struct sockaddr_in peer_addr;
    socklen_t peer_addr_size = sizeof(peer_addr);
    char buf[BUF_SIZE];
    int nbuf;
    
    while (1) {
        if ((connfd = accept(listenfd, (struct sockaddr *)&peer_addr, &peer_addr_size)) < 0) {
            perror("accept_listenfd error");
            continue;
        }


        nbuf = recv(connfd, buf, BUF_SIZE, 0);
        buf[nbuf] = '\0';

        if (!strcmp(buf, "exit")) {
            printf("exit_server\n");
            close(connfd);
            break;
        }

        printf("new conn(%s:%d); msg: %s\n", inet_ntoa(peer_addr.sin_addr), ntohs(peer_addr.sin_port), buf);
        send(connfd, buf, nbuf, 0);
        close(connfd);
    }

    close(listenfd);

    return 0;
}

TCP客户端

connect函数

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);:连接socket套接字

  • sockfd:输入参数,客户端的套接字
  • addr:输入参数,服务器的地址
  • addrlen:输入参数,服务器的地址的长度
  • 返回值:成功返回0,失败返回-1,并设置errno

程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

#define SERV_PORT 4160
#define BUF_SIZE 128

int main(int argc, char *argv[]) {
    if (argc < 2) {
        fprintf(stderr, "usage: %s <MSG>\n", argv[0]);
        return -1;
    }

    // 1. create socket
    int sockfd;
    if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        perror("create_sockfd error");
        return -1;
    }

    // 2. connect to server
    struct sockaddr_in servaddr;
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(SERV_PORT);
    if (inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr) <= 0) {
        perror("convert_servaddr error");
        return -1;
    }

    if (connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) {
        perror("connect_sockfd error");
        return -1;
    }

    // 3. send message to server
    char buf[BUF_SIZE];
    int nbuf = strlen(argv[1]);
    send(sockfd, argv[1], nbuf, 0);
    nbuf = recv(sockfd, buf, BUF_SIZE, 0);
    buf[nbuf] = '\0';
    printf("echo msg: %s\n", buf);

    close(sockfd);

    return 0;
}
本文由作者按照 CC BY 4.0 进行授权