1 理解网络编程和套接字

套接字是网络数据传输用的软件。

TCP套接字可以看做一个电话机☎️,接收电话需要电话机。同理,接收网络数据就需要一个套接字。

bind函数:分配电话号码(IP地址和端口号)

listen函数:电话开机,准备接听(将socket设置为可接收连接状态)

accetp函数:接听电话

综上可以总结一下服务端套接字创建和接收消息的过程:

  1. 调用socket创建套接字;
  2. 调用bind分配IP和端口;
  3. 调用listen将套接字转为可接收请求状态;
  4. 调用accept处理请求; ```c

    include

    include

    include

    include

    include

    include

    void error_handling(char *message);

int main(int argc, char *argv[]){ int serv_sock; int clnt_sock;

  1. struct sockaddr_in serv_addr;
  2. struct sockaddr_in clnt_addr;
  3. socklen_t clnt_addr_size;
  4. char message[] = "Hello World!";
  5. if (argc != 2){
  6. printf("Usage : %s <port>\n", argv[0]);
  7. exit(1);
  8. }
  9. //调用 socket 函数创建套接字
  10. serv_sock = socket(PF_INET, SOCK_STREAM, 0);
  11. if (serv_sock == -1)
  12. error_handling("socket() error");
  13. memset(&serv_addr, 0, sizeof(serv_addr));
  14. serv_addr.sin_family = AF_INET;
  15. serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
  16. serv_addr.sin_port = htons(atoi(argv[1]));
  17. //调用 bind 函数分配ip地址和端口号
  18. if (bind(serv_sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == -1)
  19. error_handling("bind() error");
  20. //调用 listen 函数将套接字转为可接受连接状态
  21. if (listen(serv_sock, 5) == -1)
  22. error_handling("listen() error");
  23. clnt_addr_size = sizeof(clnt_addr);
  24. //调用 accept 函数受理连接请求。如果在没有连接请求的情况下调用该函数,
  25. //则不会返回,直到有连接请求为止
  26. clnt_sock = accept(serv_sock, (struct sockaddr *)&clnt_addr, &clnt_addr_size);
  27. if (clnt_sock == -1)
  28. error_handling("accept() error");
  29. //稍后要将介绍的 write 函数用于传输数据,
  30. //若程序经过 accept 这一行执行到本行,则说明已经有了连接请求
  31. write(clnt_sock, message, sizeof(message));
  32. close(clnt_sock);
  33. close(serv_sock);
  34. return 0;

}

void error_handling(char *message){ fputs(message, stderr); fputc(‘\n’, stderr); exit(1); }

  1. 用户端发送请求步骤:
  2. 1. 调用socket创建套接字;
  3. 1. 调用connect连接指定IP + Port
  4. 1. 返回响应;
  5. ```c
  6. #include <stdio.h>
  7. #include <stdlib.h>
  8. #include <string.h>
  9. #include <unistd.h>
  10. #include <arpa/inet.h>
  11. #include <sys/socket.h>
  12. void error_handling(char *message);
  13. int main(int argc, char *argv[])
  14. {
  15. int sock;
  16. struct sockaddr_in serv_addr;
  17. char message[30];
  18. int str_len;
  19. if (argc != 3)
  20. {
  21. printf("Usage : %s <IP> <port>\n", argv[0]);
  22. exit(1);
  23. }
  24. //创建套接字,此时套接字并不马上分为服务端和客户端。
  25. //如果紧接着调用 bind,listen 函数,将成为服务器套接字
  26. //如果调用 connect 函数,将成为客户端套接字
  27. sock = socket(PF_INET, SOCK_STREAM, 0);
  28. if (sock == -1)
  29. error_handling("socket() error");
  30. memset(&serv_addr, 0, sizeof(serv_addr));
  31. serv_addr.sin_family = AF_INET;
  32. serv_addr.sin_addr.s_addr = inet_addr(argv[1]);
  33. serv_addr.sin_port = htons(atoi(argv[2]));
  34. //调用 connect 函数向服务器发送连接请求
  35. if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == -1)
  36. error_handling("connect() error!");
  37. str_len = read(sock, message, sizeof(message) - 1);
  38. if (str_len == -1)
  39. error_handling("read() error!");
  40. printf("Message from server : %s \n", message);
  41. close(sock);
  42. return 0;
  43. }
  44. void error_handling(char *message)
  45. {
  46. fputs(message, stderr);
  47. fputc('\n', stderr);
  48. exit(1);
  49. }

2 基于Linux的文件操作

在Linux中,socket被视为文件的一种。

文件创建成功会返回一个大于2的文件描述符(0、1、2为标准输入、输出、错误),socket也不例外,创建失败则返回-1。

打开文件

  1. #include <sys/types.h>
  2. #include <sys/stat.h>
  3. #include <fcntl.h>
  4. int open(const char *path, int flag);
  5. /*
  6. 成功时返回文件描述符,失败时返回-1
  7. path : 文件名的字符串地址
  8. flag : 文件打开模式信息
  9. */

文件打开模式如下表:

打开模式 含义
O_CREAT 必要时创建文件
O_TRUNC 删除全部现有数据
O_APPEND 维持现有数据,追加到其后面
O_RDONLY 只读打开
O_WRONLY 只写打开
O_RDWR 读写打开

关闭文件

  1. #include <unistd.h>
  2. int close(int fd);
  3. /*
  4. 成功时返回 0 ,失败时返回 -1
  5. fd : 需要关闭的文件或套接字的文件描述符
  6. */

写入文件

  1. #include <unistd.h>
  2. ssize_t write(int fd, const void *buf, size_t nbytes);
  3. /*
  4. 成功时返回写入的字节数 ,失败时返回 -1
  5. fd : 显示数据传输对象的文件描述符
  6. buf : 保存要传输数据的缓冲地址
  7. nbytes : 要传输数据的字节数
  8. */

在此函数的定义中,size_t 是通过 typedef 声明的 unsigned int 类型。对 ssize_t 来说,ssize_t 前面多加的 s 代表 signed ,即 ssize_t 是通过 typedef 声明的 signed int 类型。

读取文件

  1. #include <unistd.h>
  2. ssize_t read(int fd, void *buf, size_t nbytes);
  3. /*
  4. 成功时返回接收的字节数(但遇到文件结尾则返回 0),失败时返回 -1
  5. fd : 显示数据接收对象的文件描述符
  6. buf : 要保存接收的数据的缓冲地址值。
  7. nbytes : 要接收数据的最大字节数
  8. */