网络|TCP/IP网络编程C01-理解网络编程和套接字

学习笔记 网络编程(套接字编程)就是编写程序使两台联网的计算机互相交换数据,而套接字就是用来连接网络的工具。
服务器端套接字

#include int socket(int domain, int type, int protocol); // 功能:创建套接字。 // 参数:domain:采取的协议族,一般为 PF_INET;type:数据传输方式,一般为 SOCK_STREAM;protocol:一般设为 0 即可。 // 返回值:成功时返回文件描述符,失败时返回 -1 int bind(int sockfd, struct sockaddr *myaddr, socklen_t addrlen); // 功能:为套接字分配地址信息。 // 参数:sockfd:要分配地址信息的套接字文件描述符;myaddr:存有地址信息的结构体变量指针;addrlen:第二个参数的长度。 // 返回值:成功时返回 0,失败时返回 -1 int listen(int sockfd, int backlog); // 功能:将套接字转换为可接收连接的状态。 // 参数:sock:希望进入等待连接请求状态的套接字文件描述符;backlog:连接请求等待队列的长度,最多使 backlog 个连接请求进入队列。 // 返回值:成功时返回 0,失败时返回 -1 int accept(int sockfd, struct sockaddr *addr, socklen_t addrlen); // 功能:受理连接请求等待队列中待处理的连接请求。 // 参数:sock:服务器套接字的文件描述符;addr:用于保存发起连接请求的客户端地址信息;addrlen:第二个参数的长度。 // 返回值:成功时返回创建的套接字文件描述符,失败时返回 -1

接受连接请求的服务器端套接字编程流程:
  1. 调用 socket 函数创建套接字;
  2. 调用 bind 函数为套接字分配 IP 地址与端口号;
  3. 调用 listen 函数将套接字转换为可接收状态;
  4. 调用 accept 函数受理连接请求。accept 会阻塞,直到有连接请求才会返回;
  5. 调用 read/write 函数进行数据交换;
  6. 调用 close 函数断开连接;
客户端套接字
#include int connect(int sockfd, struct sockaddr *serv_addr, socklen_t addrlen); // 功能:请求连接。 // 参数:sock:客户端套接字的文件描述符;serv_addr:保存目标服务器端地址信息的结构体指针;addrlen:第二个参数的长度(单位是字节) // 返回值:成功时返回 0,失败时返回 -1

客户端请求连接步骤:
  1. 调用 socket 函数创建套接字;
  2. 调用 connect 函数请求连接;
  3. 调用 read/write 函数进行数据交换;
  4. 调用 close 函数断开连接;
客户端的 IP 地址和端口在调用 connect 函数时自动分配,无需调用 bind 函数。
基于Linux的文件操作
//file descriptor#include// fcntl.h 和 unistd.h 包含的内容有些相似,包括 open 函数等。总之使用文件函数时将 fcntl.h 和 unistd.h 都 include 就可以了 #include int open(const char *path, int flag); // 功能:按 flag 指定的模式打开文件。 // 参数:path:文件名的地址;flag:文件打开的模式。 // 返回值:成功时返回文件描述符,失败时返回 -1 int close(int fd); // 功能:关闭 fd 对应的文件或套接字。当关闭一个套接字时会向对方发送 EOF。 // 参数:fd:文件或套接字的文件描述符。 // 返回值:成功时返回 0,失败时返回 -1 ssize_t read(int fd, void* buf, size_t nbytes); // 功能:从文件 fd 读取数据。read 函数会阻塞,直到读取到数据或 EOF 才返回。 // 参数:fd:文件描述符;buf:保存要接收的数据;nbytes:要接收的最大字节数。 // 返回值:成功时返回接收的字节数(遇到文件尾则返回 0),失败时返回 -1 ssize_t write(int fd, const void* buf, size_t nbytes); // 功能:向文件 fd 输出数据。 // 参数:fd:文件描述符;buf:要传输的数据;nbytes:要传输的字节数。 // 返回值:成功时返回写入的字节数,失败时返回 -1

EOF 即表示文件尾。
size_t 的类型是 unsigned int,ssize_t 的类型是 signed int。
文件描述符 (文件句柄)
  • Linux 中套接字描述符也是文件,因此通过套接字发送、接收数据就和读写文件一样,通过 read、write 这些函数来接收、发送数据。
  • 文件描述符是系统分配给文件或套接字的整数。
  • 0、1、2 分别由系统分配给了标准输入、标准输出和标准错误。
  • 文件和套接字创建时才会被分配文件描述符。它们的文件描述符会从 3 开始按序递增。
  • Windows 系统中术语”句柄“和 Linux 中的文件描述符含义相同。
文件描述符 对象
0 标准输入:Standard Input
1 标准输出:Standard Output
2 标准错误:Standard Error
文件打开模式(flag)
用位或运算|组合多个模式
打开模式 含义
O_CREAT 必要时创建文件
O_TRUNC 删除全部现有数据
O_APPEND 追加到已有数据后面
O_RDONLY 只读打开
O_WRONLY 只写打开
O_RDWR 读写打开
习题答案 Q01 套接字在网络编程中的作用是什么?为什么称它为套接字?
P2,网络编程就是编写程序让两台联网的计算机相互交换数据。在我们不需要考虑物理连接的情况下,我们只需要考虑如何编写传输软件。操作系统提供了名为 “套接字”,套接字是网络传输用的软件设备
socket 英文原意是插座:我们把插头插到插座上就能从电网获得电力供给,同样,为了与远程计算机进行数据传输,需要连接到 Internet, 而变成中的 “套接字” 就是用来连接该网络的工具
Q02 在服务器端创建套接字后,会依次调用 listen 函数和 accept 函数。请比较并说明两者作用
listen: 调用 listen 函数将套接字转换成可受连接状态(监听)
accept: 调用 accept 函数受理连接请求,并且在没有连接请求的情况调用该函数,不会返回。直到有连接请求为止。二者存在逻辑上的先后关系
Q03 Linux 中,对套接字数据进行 I/O 时可以直接使用 I/O 相关函数;而在 Windows 中则不可以。原因为何?
Linux 把套接字也看作是文件,所以可以用文件 I/O 相关函数;而 Windows 要区分套接字和文件,所以设置了特殊的函数
Q04 创建套接字后一般会给它分配地址,为什么?为了完成地址分配需要调用哪些函数?
套接字被创建之后,只有为其分配了IP地址和端口号后,客户端才能够通过IP地址及端口号与服务器端建立连接,需要调用 bind 函数来完成地址分配。分配地址是通过bind()函数实现
Q05 Linux 中的文件描述符与 Windows 的句柄实际上非常类似。请以套接字为对象说明他们的含义。
Linux 的文件描述符是为了区分指定文件而赋予文件的整数值(相当于编号)。Windows 的文件描述符其实也是套接字的整数值,其目的也是区分指定套接字。
Q06 底层文件 I/O 函数与 ANSI 标准定义的文件 I/O 函数之间有何区别?
在ANSI标准中定义的I/O函数是作为C的标准提供的函数,无论操作系统如何,随时都可以调用。另一方面,低级文件I/O函数是操作系统提供的I/O函数。
Q07 参考本书给出的示例 low_open.c 和 low_read.c, 分别利用底层文件 I/O 和 ANSI 标准 I/O 编写文件复制程序。可任意指定复制程序的使用方法。
/*****************************low_cpy.c(底层文件I/O)*********************************/ #include #include #include #define BUF_SIZE 100int main(int argc, char *argv[]) { int src, dst; int read_cnt; char buf[BUF_SIZE]; src=https://www.it610.com/article/open("src.dat", O_RDONLY); //通过调用open来打开文件 dst=open("dst.dat", O_CREAT|O_WRONLY|O_TRUNC); if(src=https://www.it610.com/article/=-1||dst==-1) { puts("file open error"); return -1; }while((read_cnt=read(src, buf, BUF_SIZE))!=0)//通过调用read来读取文件 write(dst, buf, read_cnt); //通过调用write来写文件close(src); close(dst); return 0; }/*****************************ansi_cpy(ANSI标准I/O )*********************************/ #include #define BUF_SIZE30int main(void) { char buf[BUF_SIZE]; int readCnt; FILE * src=https://www.it610.com/article/fopen("src.dat", "rb"); //通过调用fopen来打开文件 FILE * des=fopen("dst.dat", "wb"); if(src=https://www.it610.com/article/=NULL || des==NULL) { puts("file open error"); return -1; }while(1) { readCnt=fread((void*)buf, 1, BUF_SIZE, src); //通过调用fread来读取文件if(readCnt

书本源码 01-hello_server.c
#include #include #include #include #include #include /* struct sockaddr_in { sa_familysin_family; //地址族(AF_INET|AF_INET6|...),两个字节 uint16_tsin_port; //16位端口号 struct in_addrsin_addr; // 表示 32 位 IP 地址的结构体 charsin_zero[8]; //占位用(必须填充为0) }struct in_addr { In_addr_ts_addr; // 32 位 IP 地址,实际位为 uint32_t 类型 } */void error_handling(char *message); int main(int argc, char *argv[]) { int serv_sock; int clnt_sock; struct sockaddr_in serv_addr; struct sockaddr_in clnt_addr; socklen_t clnt_addr_size; char message[]="Hello World!"; if(argc!=2){ printf("Usage : %s \n", argv[0]); exit(1); } serv_sock=socket(PF_INET, SOCK_STREAM, 0); //第一步socket if(serv_sock == -1) error_handling("socket() error"); //填写服务器端的IP地址和端口 memset(&serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family=AF_INET; serv_addr.sin_addr.s_addr=htonl(INADDR_ANY); serv_addr.sin_port=htons(atoi(argv[1])); if(bind(serv_sock, (struct sockaddr*) &serv_addr, sizeof(serv_addr))==-1 )//第二步bind error_handling("bind() error"); if(listen(serv_sock, 5)==-1)//第三步listen error_handling("listen() error"); clnt_addr_size=sizeof(clnt_addr); clnt_sock=accept(serv_sock, (struct sockaddr*)&clnt_addr,&clnt_addr_size); //第四步accept if(clnt_sock==-1) error_handling("accept() error"); write(clnt_sock, message, sizeof(message)); //发送消息 close(clnt_sock); close(serv_sock); return 0; }void error_handling(char *message) { fputs(message, stderr); fputc('\n', stderr); exit(1); }/************** 输入******************/******************** input****************** description: 建立tcp服务器端。输入需要指定端口号content: ./01-hello_server9190*******************************************//******************** output****************** description: 无输出content:*******************************************/

02-hello_client.c
#include #include #include #include #include #include void error_handling(char *message); int main(int argc, char* argv[]) { int sock; struct sockaddr_in serv_addr; char message[30]; int str_len; if(argc!=3){ printf("Usage : %s 【网络|TCP/IP网络编程C01-理解网络编程和套接字】\n", argv[0]); exit(1); } sock=socket(PF_INET, SOCK_STREAM, 0); //第一步socket if(sock == -1) error_handling("socket() error"); //填写目的端的IP地址和端口 memset(&serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family=AF_INET; serv_addr.sin_addr.s_addr=inet_addr(argv[1]); serv_addr.sin_port=htons(atoi(argv[2])); if(connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr))==-1) //第二步connect error_handling("connect() error!"); str_len=read(sock, message, sizeof(message)-1); if(str_len==-1) error_handling("read() error!"); printf("Message from server: %s \n", message); close(sock); return 0; }void error_handling(char *message) { fputs(message, stderr); fputc('\n', stderr); exit(1); }/******************** input****************** description: 建立tcp客户端,需要制定ip和端口号content: ./02-hello_client127.0.0.1 9190*******************************************//******************** output****************** description:接收来自服务器端的消息content: Message from server: Hello World! *******************************************/

03-low_open.c
#include #include #include #include void error_handling(char* message); int main(void) { int fd; char buf[]="Let's go!\n"; fd=open("data.txt", O_CREAT|O_WRONLY|O_TRUNC); //用或运算追加条件 if(fd==-1) error_handling("open() error!"); printf("file descriptor: %d \n", fd); if(write(fd, buf, sizeof(buf))==-1)//将内容写进文件里 error_handling("write() error!"); close(fd); return 0; }void error_handling(char* message) { fputs(message, stderr); fputc('\n', stderr); exit(1); }/******************** input****************** description:content:cat data.txt*******************************************//******************** output****************** description:content: file descriptor: 3Let's go! *******************************************/

04-low_read.c
#include #include #include #include #define BUF_SIZE 100void error_handling(char* message); int main(void) { int fd; char buf[BUF_SIZE]; fd=open("data.txt", O_RDONLY); //open if( fd==-1) error_handling("open() error!"); printf("file descriptor: %d \n" , fd); if(read(fd, buf, sizeof(buf))==-1)//read error_handling("read() error!"); printf("file data: %s", buf); close(fd); return 0; }void error_handling(char* message) { fputs(message, stderr); fputc('\n', stderr); exit(1); }/******************** input****************** description:content:*******************************************//******************** output****************** description:content: file descriptor: 3 file data: Let's go!*******************************************/

05-fd_seri.c
#include #include #include #include int main(void) { int fd1, fd2, fd3; fd1=socket(PF_INET, SOCK_STREAM, 0); fd2=open("test.dat", O_CREAT|O_WRONLY|O_TRUNC); fd3=socket(PF_INET, SOCK_DGRAM, 0); printf("file descriptor 1: %d\n", fd1); printf("file descriptor 2: %d\n", fd2); printf("file descriptor 3: %d\n", fd3); close(fd1); close(fd2); close(fd3); return 0; }/******************** input****************** description:content:*******************************************//******************** output****************** description:描述符从3开始以由小到大的顺序编号( numbering),0,1,2已经被占用content: file descriptor 1: 3 file descriptor 2: 4 file descriptor 3: 5*******************************************/

参考链接
  • 《TCP/IP网络编程》课后练习答案第一部分1~5章 尹圣雨_KongJHong的博客-CSDN博客_tcpip网络编程课后答案
  • phoon/TCP-IP-NP: 《TCP/IP网络编程》((韩)尹圣雨) 学习笔记
  • 《TCP/IP网络编程》学习笔记整理 - 从园客博开始 - 博客园
  • riba2534/TCP-IP-NetworkNote: 《TCP/IP网络编程》(韩-尹圣雨)学习笔记
  • hclg/tcp_ip: TCP/IP 网络编程

    推荐阅读