学习笔记 网络编程(套接字编程)就是编写程序使两台联网的计算机互相交换数据,而套接字就是用来连接网络的工具。
服务器端套接字
#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
接受连接请求的服务器端套接字编程流程:
- 调用 socket 函数创建套接字;
- 调用 bind 函数为套接字分配 IP 地址与端口号;
- 调用 listen 函数将套接字转换为可接收状态;
- 调用 accept 函数受理连接请求。accept 会阻塞,直到有连接请求才会返回;
- 调用 read/write 函数进行数据交换;
- 调用 close 函数断开连接;
#include
int connect(int sockfd, struct sockaddr *serv_addr, socklen_t addrlen);
// 功能:请求连接。
// 参数:sock:客户端套接字的文件描述符;serv_addr:保存目标服务器端地址信息的结构体指针;addrlen:第二个参数的长度(单位是字节)
// 返回值:成功时返回 0,失败时返回 -1
客户端请求连接步骤:
- 调用 socket 函数创建套接字;
- 调用 connect 函数请求连接;
- 调用 read/write 函数进行数据交换;
- 调用 close 函数断开连接;
基于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 |
用位或运算
|
组合多个模式打开模式 | 含义 |
---|---|
O_CREAT | 必要时创建文件 |
O_TRUNC | 删除全部现有数据 |
O_APPEND | 追加到已有数据后面 |
O_RDONLY | 只读打开 |
O_WRONLY | 只写打开 |
O_RDWR | 读写打开 |
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 网络编程
推荐阅读
- 网络|计算机网络-网络安全
- k8s|k8s flannel
- 网络|简单易用的嵌入式网络库(Mongoose)
- 交换机|第16章 以太网交换机工作原理(H3CNE)
- 网络运营|做网络必须掌握83句话,网络成功可以复制
- TCP、UDP、IP、以太网Ethernet协议报文详解以及使用Wildpackets Omnipeek网络抓包分析
- socket TCP长连接和UDP网络编程实例、阻塞与非阻塞select、常见面试题
- Linux socket网络编程(一)(TCP编程实例、socket编程函数、套接字类型和网络模型)
- TCP/IP五层模型之数据包发送流程