1.网络编程和套接字
网络编程与C语言中的printf函数和scanf函数以及文件的输入输出类似,本质上也是一种基于I/O的编程方法。之所以这么说,是因为网络编程大多是基于套接字(socket,网络数据传输的软件设备,操作系统为我们提供的编程接口)来实现数据的输入输出的。
套接字通信过程可以类比打电话的过程。电话机可以用来拔打和接听,但对于套接字而言,拔打和接听是有区别的。
构建接电话套接字
调用socket函数安装电话机
#includeint socket(int domain, int type, int protocol); -> 成功时返回文件描述符,失败时返回-1
调用bind函数分配电话号码
#includeint bind(int sockfd, struct sockaddr *myaddr, socklen_t addrlen); -> 成功时返回0,失败时返回-1
调用listen函数连接电话
#includeint listen(int sockfd, int backlog); -> 成功时返回0,失败时返回-1
调用accept函数接听电话
#includeint accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); -> 成功时返回文件描述符,失败时返回-1
网络编程中接受连接请求的套接字创建过程总结如下:
1.调用socket函数创建套接字
2.调用bind函数分配IP地址和端口号
3.调用了listen函数转换为可接收请求状态
4.调用accept函数受理连接请求
1 #include2 #include 3 #include <string.h> 4 #include 5 #include 6 #include 7 8 void error_handling(char *message); 9 10 int main(int argc, char *argv[]) 11 { 12 int serv_sock; 13 int clnt_sock; 14 15 struct sockaddr_in serv_addr; 16 struct sockaddr_in clnt_addr; 17 socklen_t clnt_addr_size; 18 19 char message[]="Hello World!"; 20 21 if(argc!=2){ 22 printf("Usage : %s \n ", argv[0]); 23 exit(1); 24 } 25 26 serv_sock=socket(PF_INET, SOCK_STREAM, 0); 27 if(serv_sock == -1) 28 error_handling("socket() error"); 29 30 memset(&serv_addr, 0, sizeof(serv_addr)); 31 serv_addr.sin_family=AF_INET; 32 serv_addr.sin_addr.s_addr=htonl(INADDR_ANY); 33 serv_addr.sin_port=htons(atoi(argv[1])); 34 35 if(bind(serv_sock, (struct sockaddr*) &serv_addr, sizeof(serv_addr))==-1 ) 36 error_handling("bind() error"); 37 38 if(listen(serv_sock, 5)==-1) 39 error_handling("listen() error"); 40 41 clnt_addr_size=sizeof(clnt_addr); 42 clnt_sock=accept(serv_sock, (struct sockaddr*)&clnt_addr,&clnt_addr_size); 43 if(clnt_sock==-1) 44 error_handling("accept() error"); 45 46 write(clnt_sock, message, sizeof(message)); 47 close(clnt_sock); 48 close(serv_sock); 49 return 0; 50 } 51 52 void error_handling(char *message) 53 { 54 fputs(message, stderr); 55 fputc('\n', stderr); 56 exit(1); 57 }
构建打电话套接字
服务器端创建的套接字又称为服务器端套接字或监听(listening)套接字。而客户端套接字则比较简单,除了创建套接字外,只需要连接过程即可。
客户端发起打电话动作
#includeint connect(int sockfd, struct sockaddr *serv_addr, socklen_t addrlen); -> 成功时返回0,失败时返回-1
网络编程中发出连接请求的套接字创建过程总结如下:
1.调用socket函数创建套接字
2.调用connect函数发出连接请求
1 #include2 #include 3 #include <string.h> 4 #include 5 #include 6 #include 7 8 void error_handling(char *message); 9 10 int main(int argc, char* argv[]) 11 { 12 int sock; 13 struct sockaddr_in serv_addr; 14 char message[30]; 15 int str_len; 16 17 if(argc!=3){ 18 printf("Usage : %s ", argv[0]); 19 exit(1); 20 } 21 22 sock=socket(PF_INET, SOCK_STREAM, 0); 23 if(sock == -1) 24 error_handling("socket() error"); 25 26 memset(&serv_addr, 0, sizeof(serv_addr)); 27 serv_addr.sin_family=AF_INET; 28 serv_addr.sin_addr.s_addr=inet_addr(argv[1]); 29 serv_addr.sin_port=htons(atoi(argv[2])); 30 31 if(connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr))==-1) 32 error_handling("connect() error!"); 33 34 str_len=read(sock, message, sizeof(message)-1); 35 if(str_len==-1) 36 error_handling("read() error!"); 37 38 printf("Message from server: %s \n", message); 39 close(sock); 40 return 0; 41 } 42 43 void error_handling(char *message) 44 { 45 fputs(message, stderr); 46 fputc('\n', stderr); 47 exit(1); 48 } \n
2.基于Linux的文件操作
在Linux的世界里,socket也被认为是文件的一种,因此在网络数据传输过程中自然可以使用文件I/O相关函数。操作文件或是套接字,首先需要了解什么是文件描述符?文件描述符是一种系统资源,通常是在文件和套接字创建过程由系统分配的一个整数。三个知名文件描述符(标准输入输出及标准错误文件描述符)如下,它们不经过特殊的创建过程,而是伴随系统运行而自动分配的。
下面介绍四个常用文件操作函数
//打开文件 #include#include #include int open(const char *path, int flag); -> 成功时返回文件描述符,失败时返回-1 path: 文件名的字符串地址 flag : 文件打开模式信息
其中,文件打开模式常用值如下,可以通过位或运算传递多个值
//关闭文件 #includeint close(int fd); -> 成功时返回0,失败时返回-1 fd: 需要关闭的文件或套接字的文件描述符
若调用此函数并传入文件描述符,则关闭相应文件,且此函数同时可以关闭套接字。
//写文件 #includessize_t write(int fd, const char *buf, size_t nbytes); -> 成功时返回写入的字节数,失败时返回-1
write函数用于向文件输入数据,当然,通过套接字向其他计算机传输数据亦可使用该函数。
//读文件 #includessize_t read(int fd, char *buf, size_t nbytes); -> 成功时返回读出的字节数(遇到文件结尾则返回0),失败时返回-1
read函数用于从文件读出数据,同样,通过套接字从其他计算机接收数据亦可使用该函数。
文件描述符与套接字的关系
通过观察下面程序运行结果可知,描述符从3开始从小到大的顺序编号,再次说明了Linux系统中套接字与文件并无差别。
#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; }
一些思考:
1.INADDR_ANY是什么地址?
2.sockaddr_in与socaddr的关系?
3.为什么客户端不需要bind函数将IP信息绑定到套接字?
4.服务端的阻塞函数是什么?
5.listen函数中,监听套接字参数sockfd的作用?与accept返回的套接字参数sockfd之间的关系?
6.listen函数中,参数backlog怎么理解?
后续系列文章中慢慢给出参考