1 TCP通信过程

TCP客户端/服务器(C/S)结构的通信过程如下:
2 TCP网络连接 - 图1
实现该通信过程需要用到的函数如下:

2 服务器端函数

2.1 socket

头文件: <sys/socket.h>
函数定义: int socket(int domain, int type, int protocol);
功能: 创建一个套接字用于通信; 返回值为socket 描述符,失败返回-1
参数:

  • domain: 指定协议族
  • type: 指定socket类型,SOCK_STREAM、SOCK_DGRAM,SOCK_RAW
  • protocol: 协议类型。指定0表示自动选择协议类型

    2.2 setsockopt

    头文件: <sys/socket.h>
    函数定义: int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
    功能: 绑定本地地址到socket; 返回值为0,失败返回-1
    参数:

  • sockfd: 要设置的socket id

  • level: 设置的级别,一般设置为SOL_SOCKET
  • optname: 设置参数名。例如地址重复利用为参数SO_REUSEADDR
  • optval: 参数值
  • optlen: 参数值的长度

    NOTE: 服务器要尽可能使用**SO_REUSEADDR**;可以不必等待端口TIME_WAIT状态消失就可以重启服务器

下图列出来常用的socket选项:
image.png

2.3 bind

头文件: <sys/socket.h>
函数定义: int bind(int sockid, const sockaddr* addr, socklen_t addrlen);
功能: 绑定本地地址到socket; 返回值为0,失败返回-1
参数:

  • addr: 要绑定的地址
  • addrlen: 地址长度

    2.4 listen

    头文件: <sys/socket.h>
    函数定义: int listen(int sockid, int backlog);
    功能: 监听socket的连接请求; 返回值为0,失败返回-1
    参数:

  • backlog: 最大连接个数

    2.5 accept

    头文件: <sys/socket.h>
    函数定义: int accept(int sockid, struct sockaddr* addr, socklen_t addrlen);
    功能: 从已完成连接的队列中返回第一个连接,队列空则继续等待;成功返回connection id,失败返回-1

    注意:accept只是从监听队列中取出连接,不关心当前连接的状态和网络的变化。

3 客户端端函数

3.1 connect

头文件: <sys/socket.h>
函数定义: int connect(int sockid, const sockaddr* addr, socklen_t addrlen);
功能: 建立一个连接到addr的客户端socket; 成功返回0,失败返回-1

4 TCP读写函数

4.1 recv

头文件: <sys/socket.h>
函数定义: ssize_t recv(int sockfd, void* buf, size_t len, int flags);
功能: 从socket获取数据; 返回读取的字节数,失败返回-1
参数:

  • flags: 常用选项:MSG_OOB(指定接收带外数据); MSG_PEEK(接收数据,并保留缓冲区数据不清除)

    4.2 send

    头文件:
    函数定义: int send(int s, const void *buf, int len, unsigned int flags);
    功能: 从socket发送数据到指定主机; 返回发送的字节数,失败返回-1

flags的常用选项如下:
image.png

5 通用读写函数

socket API提供了一堆通用的读写函数,能用于TCP和UDP,定义如下:

  1. ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
  2. ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
  3. //msgHdr结构体定义如下:
  4. struct msghdr
  5. {
  6. void *msg_name; //指向socket变量,对于TCP设置为NULL
  7. socklen_t msg_namelen; //socket地址长度
  8. struct iovec *msg_iov; //内存块数组
  9. size_t msg_iovlen; //msg_iov内存块个数
  10. void *msg_control; /* Ancillary data*/
  11. size_t msg_controllen; /* Ancillary data buffer len */
  12. int msg_flags; /* Flags (unused) */
  13. };

msghdr与其他结构体的关系如下:
1547822178099.png

6 关闭连接函数

  • int close(int fd);:将fd的引用计数减一,当引用计数为0时,关闭连接或文件。需要注意的是多进程中父进程和子进程都要执行close操作,不然引用计数一直不为0。
  • int shutdown(int sockfd, int how);:立刻终止连接,不是减少引用计数。how参数用以决定具体的行为,取值如下:
    • SHUT_RD:关闭socket的读
    • SHUT_WR:关闭socket的写
    • SHUT_RDWR:同时关闭socket读写

      7 完整代码示例

      服务器端

      ```c

      include

      include

      include

      include

      include

int main() { //create socket int socketId; if ((socketId = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) { printf(“socket create failed\r\n”); }

  1. sockaddr_in addr;
  2. memset(&addr, 0, sizeof(addr));
  3. addr.sin_family = AF_INET; //指定协议
  4. addr.sin_port = htons(5188); //指定网络字节序格式的端口号
  5. /** addr.sin_addr = htonl(INADDR_ANY); //指定本地任意ip地址,使用网络字节序 */
  6. addr.sin_addr.s_addr = inet_addr("127.0.0.1");
  7. //start bind
  8. if (bind(socketId, (sockaddr*)&addr, sizeof(addr)) < 0)
  9. {
  10. printf("socket bind failed\r\n");
  11. }
  12. //start listen
  13. if (listen(socketId, SOMAXCONN) < 0)
  14. {
  15. printf("socket listen failed\r\n");
  16. }
  17. //get connection from socket queue
  18. sockaddr remoteAddr;
  19. socklen_t remoteLen = sizeof(remoteAddr);
  20. int remoteConnId;
  21. pid_t pid;
  22. while (1)
  23. {
  24. //get connection from socket queue
  25. if ((remoteConnId = accept(socketId, (sockaddr*)&remoteAddr, &remoteLen)) < 0)
  26. {
  27. printf("socket accept failed\r\n");
  28. break;
  29. }
  30. pid = fork(); //创建新进程处理socket连接
  31. if (pid == -1)
  32. {
  33. printf("create new process failed\r\n");
  34. break;
  35. }
  36. if (pid == 0)
  37. {
  38. //子进程不需要处理socket监听,关闭监听
  39. close(socketId);
  40. //子进程处理connection
  41. char rcvBuf[1024];
  42. while(1)
  43. {
  44. memset(rcvBuf, 0, sizeof(rcvBuf));
  45. int ret = read(remoteConnId, rcvBuf,sizeof(rcvBuf));// 从socket中读取数据流
  46. if (ret == 0)
  47. {
  48. printf("close client: [%d]\r\n", remoteConnId);
  49. break;
  50. }
  51. fputs(rcvBuf, stdout);
  52. write(remoteConnId, rcvBuf, strlen(rcvBuf));//将数据再写回remote端
  53. }
  54. close(remoteConnId);
  55. //退出子进程
  56. _exit(0);
  57. }
  58. else
  59. {
  60. //父进程不需要处理socket连接,关闭connection
  61. close(remoteConnId);
  62. }
  63. }
  64. close(socketId);
  65. return 0;

}

  1. <a name="IIOiZ"></a>
  2. ## 客户端
  3. ```c
  4. #include <stdio.h>
  5. #include <sys/socket.h>
  6. #include <arpa/inet.h>
  7. #include <string.h>
  8. #include <unistd.h>
  9. int main()
  10. {
  11. //create socket
  12. int socketId;
  13. if ((socketId = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
  14. {
  15. printf("socket create failed\r\n");
  16. }
  17. sockaddr_in addr;
  18. memset(&addr, 0, sizeof(addr));
  19. addr.sin_family = AF_INET; //指定协议
  20. addr.sin_port = htons(5188); //指定网络字节序格式的端口号
  21. addr.sin_addr.s_addr = inet_addr("127.0.0.1");
  22. //connect to server via socket
  23. if (connect(socketId, (sockaddr*)&addr, sizeof(addr)) < 0)
  24. {
  25. printf("connect to server failed\r\n");
  26. }
  27. char buf[1024];
  28. while (fgets(buf, sizeof(buf), stdin) != NULL)
  29. {
  30. write(socketId, buf, strlen(buf));
  31. read(socketId, buf, sizeof(buf));//read reply from server
  32. printf("received from server: [%s]\r\n", buf);
  33. memset(buf, 0, sizeof(buf));
  34. }
  35. close(socketId);
  36. return 0;
  37. }