Linux网络编程学习笔记(二)socket操作

这里是本人对Linux网络编程的学习做一个记录,主要参考书籍如下:

  • Linux高性能服务器编程
  • Unix网络编程 卷1:套接字联网API
    socket是一个实现内核空间与用户空间的通信的接口。

一、socket服务端

提供网络服务的一端

1、socket服务端操作过程
(1)图解socket服务端操作过程

光看图不过瘾?来上一锅代码

(2)代码实现socket服务端操作过程
  1. #include <stdio.h>
  2. #include <sys/socket.h>
  3. #include <netinet/in.h>
  4. #include <arpa/inet.h>
  5. #include <sys/types.h>
  6. #include <sys/errno.h>
  7. #include <unistd.h>
  8. #include <string.h>
  9. #define BACKLOG_LENGTH 5
  10. #define BUF_SIZE 100
  11. int main(void)
  12. {
  13. //第一步:创建socket
  14. int sock = socket(PF_INET, SOCK_STREAM, 0);
  15. if (sock < 0) {
  16. printf("socket create failed,the error is:%s\n", strerror(errno));
  17. return -1;
  18. }
  19. //第二步:为socket绑定地址(命名socket)
  20. struct sockaddr_in saddr;
  21. memset(&saddr, '\0', sizeof(saddr));
  22. saddr.sin_family = AF_INET;
  23. saddr.sin_addr.s_addr = inet_addr("127.0.0.1");
  24. saddr.sin_port = htons(9669);
  25. int bin = bind(sock, (struct sockaddr *)&saddr, sizeof(saddr));
  26. if (bin < 0) {
  27. printf("bind failed,the error is:%s\n", strerror(errno));
  28. return -1;
  29. }
  30. //第三步:监听socket
  31. int status = listen(sock, BACKLOG_LENGTH);
  32. if (status < 0) {
  33. printf("listen failed,the error is:%s\n", strerror(errno));
  34. return -1;
  35. }
  36. //第四步:接受连接
  37. struct sockaddr_in client;
  38. socklen_t client_len = sizeof(client);
  39. int conn = accept(sock, (struct sockaddr *)&client, &client_len);
  40. if (conn < 0) {
  41. printf("accept failed,the error is:%s", strerror(errno));
  42. return -1;
  43. }
  44. //第五步:读写数据(接收和发送数据)
  45. char buf[BUF_SIZE];
  46. memset(buf, '\0', BUF_SIZE);
  47. char msg[BUF_SIZE] = "this is a socket server";
  48. int res;
  49. res = recv(conn, buf, BUF_SIZE -1, 0);
  50. printf("result:%d,data:%s\n", strerror(errno), buf);
  51. //发送数据
  52. int send_len = send(conn, msg, sizeof(msg), 0);
  53. if (send_len <= 0) {
  54. printf("send data failed,the error is:%s", strerror(errno));
  55. }
  56. //第六步:关闭连接(头文件unistd.h)
  57. close(conn);
  58. //第七步:关闭socket(头文件unistd.h)
  59. close(sock);
  60. return 0;
  61. }

注:

  • 创建socket的时候要注意选择合适的类型,这里选择SOCK_STREAM(流式,一般TCP选择这种类型;另外还有数据报式SOCK_DGRAM,一般UDP选择这种),SOCK_STREAM可以使用listen监听,否则会报operation not supported的错误
(3)运行代码看疗效

编译运行上面的代码(文件名为socket_server.c)

  1. gcc socket_server.c -o socket_server
  2. sudo ./socket_server

使用telnet模拟客户端连接socket server并发送一个”hello world”的数据

  1. telnet 127.0.0.1 9669
  2. hello world

查看socket server运行效果:

查看telnet 运行效果

注:如果未安装telnet,可以使用yum或者apt-get安装

(4)功能优化
  • socket server代码后,客户端(telnet)发送一次数据,它就关闭了,如果再使用telent发送数据,需要重新运行,是因为它接收数据后,就关闭了连接,最后关闭了socket,并返回了,所以这里改造一下:

  • 将第四步、第五步和第六步放到一个无限循环中,那么它就可以一直运行,不会退出,可以支持新的连接(将第四、五、六步的代码修改为以下的形式)

  1. struct sockaddr_in client;
  2. socklen_t client_len = sizeof(client);
  3. char buf[BUF_SIZE];
  4. memset(buf, '\0', BUF_SIZE);
  5. char msg[BUF_SIZE];
  6. int res;
  7. while(1) {
  8. //第四步:接受连接
  9. int conn = accept(sock, (struct sockaddr *)&client, &client_len);
  10. if (conn < 0) {
  11. printf("accept failed,the error is:%s", strerror(errno));
  12. return -1;
  13. }
  14. //第五步:接收数据
  15. res = recv(conn, buf, BUF_SIZE -1, 0);
  16. printf("result:%d,data:%s\n", strerror(errno), buf);
  17. //发送数据
  18. int send_len = send(conn, msg, sizeof(msg), 0);
  19. if (send_len <= 0) {
  20. printf("send data failed,the error is:%s", strerror(errno));
  21. }
  22. //第六步:关闭连接(头文件unistd.h)
  23. close(conn);
  24. }
2、知识拾遗——socket服务端代码说明以及socket函数说明
(1)头文件
  • stdio.h :标准输入输出库,主要提供输入输出的一些函数(如printf)
  • sys/socket.h:系统socket操作库,提供socket操作的相关函数(如socket,listen等)
  • netinet/in.h:网络中的ipv4地址相关结构的定义(sockaddr_in等结构体的定义)
  • arpa/inet.h:ipv4地址转换函数库(如inet_addr,inet_aton,inet_ntoa等)
  • sys/types.h:系统中数据定义别名定义(如size_t,__socklen_t等)
  • sys/errno.h:错误相关的全局变量(如errno)
  • unistd.h:unix系统标准函数库(如close函数)
  • string.h:字符串操作相关函数库(包括字符串内存分配相关,如memset)
(2)socket操作函数
  • socket

    • 作用:创建socket
    • 头文件:
    • 原型:extern int socket (int domain, int type, int protocol)
    • 参数:
      domain:使用的协议族(PF_UNIX本地unix域,PF_INET ipv4协议,PF_INET6 ipv6协议)
      type:使用的服务类型(SOCK_STREAM 流服务,一般是TCP协议使用的;SOCK_DGRAM 数据报服务,一般是UDP协议使用的)
      protocol:使用的协议(通常前两个参数已经决定了它使用的的协议,这里通常使用0)
    • 返回值:成功则返回一个文件描述符,失败就返回-1(通过errno来获取具体错误)
  • bind

    • 作用:绑定socket(将socket与IP地址结构体变量进行绑定)
    • 头文件:
    • 原型:extern int bind (int fd, const struct sockaddr * addr, socklen_t len)
    • 参数:
      fd:socket文件描述符
      addr:地址结构体指针变量
      len:地址结构体变量的长度(所占内存大小)
    • 返回值:成功则返回0,失败返回-1 (通过errno来获取具体错误)
  • listen

    • 作用:监听socket
    • 头文件:
    • 原型:extern int listen (int fd, int backlog)
    • 参数:
      fd:socket文件描述符
      backlog:内核监听队列的最大长度

    • 返回值:成功则返回0,失败返回-1 (通过errno来获取具体错误)

  • accept

    • 作用:接受客户端连接(个人认为是建立连接)
    • 头文件:
    • 原型:extern int accept (int fd, struct sockaddr addr,socklen_t addr_len);
    • 参数:
      fd:socket文件描述符
      sockaddr:客户端地址结构体指针
      addr_len:客户端地址结构体长度的指针
    • 返回值:成功则返回连接文件描述符,失败返回-1 (通过errno来获取具体错误)
  • recv

    • 作用:接收socket另一端发送的数据
    • 头文件:
    • 原型:extern ssize_t recv (int conn_fd, void * buf, size_t buf_len, int flags);
    • 参数:
      conn_fd:服务端与客户端连接文件描述符
      buf:用于存放接收的数据的缓冲区指针
      buf_len:存放接收的数据的缓冲区长度
      flags:数据收发额外控制的参数(MSG_CONFIRM等)
    • 返回值:成功则返回0,失败返回-1 (通过errno来获取具体错误)
  • send

    • 作用:向socket另一端发送数据
    • 头文件:
    • 原型:extern ssize_t send (int conn_fd, const void *buf, size_t n, int flags);
    • 参数:
      conn_fd:服务端与客户端连接文件描述符
      buf:用于存放发送的数据的缓冲区指针
      buf_len:存放发送的数据的缓冲区长度
      flags:数据收发额外控制的参数(MSG_CONFIRM等)

    • 返回值:成功则返回0,失败返回-1 (通过errno来获取具体错误)

(3)其他结构体与函数
  • sockaddr_in

    • 作用:ipv4的地址结构体
    • 头文件:
    • 成员变量:
      sa_family_t sin_family:使用的地址协议族
      in_port_t sin_port:整型的端口号
      struct in_addr sin_addr:ipv4结构体变量
      inet_addr

    • 作用:将字符串型的ip地址转换为整型的ipv4地址(在前面的文章中有细述)

  • htons

  • 作用:是将整型变量从主机字节顺序转变成网络字节顺序,就是整数在地址空间存储方式变为:高位字节存放在内存的低地址处
  • memset

    • 作用:为一个指针分配指定的内存,并使用指定的字符填充
    • 头文件:
    • 原型:extern void memset (void s, int c, size_t n)
    • 参数:
      s:被操作的指针变量
      c:用来填充的字符
      n:填充的长度
  • close

    • 作用:关闭一个文件描述符
    • 头文件:
    • 原型:extern int close (int fd)
    • 参数:
      fd:文件描述符

二、socket客户端

连接socket服务器的一端

1、socket客户端操作过程
(1)图解socket服务端操作过程

有图有真相

图样图simple,好吧,看点实际的(它确实比socket服务端简单一些)

(2)代码实现socket客户端操作过程
  1. #include <sys/socket.h>
  2. #include <arpa/inet.h>
  3. #include <netinet/in.h>
  4. #include <errno.h>
  5. #include <string.h>
  6. #include <unistd.h>
  7. #include <stdio.h>
  8. #define BUF_SIZE 100
  9. int main(void)
  10. {
  11. //第一步:创建socket
  12. int sock = socket(AF_INET, SOCK_STREAM, 0);
  13. if (sock < 0) {
  14. printf("create socket failed,the error is:%s\n", strerror(errno));
  15. return -1;
  16. }
  17. //第二步:与服务端建立连接
  18. struct sockaddr_in saddr;
  19. saddr.sin_family = AF_INET;
  20. saddr.sin_port = htons(9669);
  21. saddr.sin_addr.s_addr = inet_addr("127.0.0.1");
  22. int conn = connect(sock, (struct sockaddr *) &saddr, sizeof(saddr));
  23. //注:这里如果连接成功返回0,否则为-1,却不是一个文件描述符
  24. if (conn < 0) {
  25. printf("connect the socket server failed,the error is:%s\n", strerror(errno));
  26. return -1;
  27. }
  28. //第三步:读写数据(接收和发送数据)
  29. char buf[BUF_SIZE];
  30. char msg[BUF_SIZE] = "welcome to client world!";
  31. int flag = recv(sock, buf, BUF_SIZE, 0);
  32. if (flag < 0) {
  33. printf("receive data failed,the error is:%s\n", strerror(errno));
  34. } else {
  35. printf("server say:%s\n", buf);
  36. }
  37. //发送数据
  38. int len = send(sock, msg, BUF_SIZE, 0);
  39. if (len <= 0) {
  40. printf("send data failed,the error is:%s\n", strerror(errno));
  41. }
  42. //第四步:关闭socket连接
  43. close(sock);
  44. return 0;
  45. }

注意点:

  • connect函数返回的是一个连接状态,而不是连接描述符,因此* 在send和recv中传入的还是socket对象