Linux操作系统下C语言网络编程(全文23475字,包含了Linux系统下所有网络编程的知识点,附程序代码)
一、简介
如今网络应用随处可见,web、http、email 等这些都是网络应用程序,他们都有着基于相同的基本编程模型,有着相似的整体逻辑结构,并且还有着相同的编程接口。我们需要了解基本的客户端-服务器编程模型。
1.1 客户端-服务器编程模型
每个应用程序都是基于客户端-服务器编程模型的,他们由一个服务器进程和多个客户端进程组成,服务器管理某种资源,通过操作这种资源来为客户端提供某种服务。例如ftp服务器管理磁盘文件,为客户端存储和检索。
客户端-服务器编程模型中的基本操作是事务,一个客户端-服务器事务由以下四步组成
- 当一个客户端需要服务时,它向服务器发送一个请求,发起一个事务
- 服务器收到请求后,解释它,并以适当的方式操作它的资源。
- 服务器给客户端一个响应,并等待下一个请求。
- 客户端收到响应并处理它。。
客户端和服务器是两个进程而不是机器或者主机,这是一个非常重要的,因为一台主机可以有多个不同的客户端和服务器。
1.2 网络
通常,客户端和服务器是在不同主机上,他们通过计算机网络硬件和软件资源来通信。网络是一个很复杂的系统(过段时间我会做一个计算机网络的专栏来讲解,这里不大体说一下)。对于操作系统来说,网络只是一个
I/O设备,是数据源和数据接收方,一个插到I/O总线扩展槽的适配器提供了网络的物理接口,从网络上接收到的数据从适配器经过I/O和内存总线复制到内存,通常是通过DMA传送,当然数据也可以从内存到网络。
如图:
1.3 两个重要的模型 OSI 和TCP IP 模型
网络将不同功能分为不同模块,以分层的形式组合在一起,每层实现不同的功能,其内部实现方法对外部来说是透明的,每层向上提供服务,同时使用下层服务。
OSI开放系统互联模型应用层 应用程序:FTP、E-mail、Telnet表示层 数据格式定义、数据转换/加密会话层 建立通信进程的逻辑名字与物理名字之间的联系 传输层 差错处理/恢复,流量控制,提供可靠的数据传输网络层 数据分组、路由选择数据链路层 数据组成可发送、接收的帧物理层 传输物理信号、接口、信号形式、速率
TCP/IP协议族:传输控制/网际协议(Transfer Control Protocol/Internet Protocol) 又称作网络通讯协议
TCP/IP协议是Internet事实上的工业标准,
应用层 TFTP,HTTP,SNMP,FTP,SMTP,DNS,Telnet 传输层 TCP,UDP 网络层 IP,ICMP,RIP,OSPF,BGP,IGMP 网络接口与物理层 SLIP,CSLIP,PPP,ARP,RARP,MTU ISO2110,IEEE802.1,EEE802.2
通信协议模型如下图
数据封装与解封过程如下图
TCP(Transport Control Protocol)传输控制协议IP(Internetworking Protocol)网间协议UDP(User Datagram Protocol)用户数据报协议SMTP(Simple Mail Transfer Protocol)简单邮件传输协议HTTP(Hypertext Transfer Protocol) 超文本传输协议FTP(File Transfer Protocol)文件传输协议ARP(Address Resolution Protocol)地址解析协议
1.4 IP地址
IP地址就是一个32位无符号的整数(IPV4)或者128位无符号的整数(IPV6),是因特网中主机的唯一标识。
ipv4表示形式:点分十进制,192.168.3.178
IP地址分类(依据ipv4前八位来区分)A类 0000 0000 - 0111 1111 0.0.0.1 - 126.255.255.255B类 1000 0000 - 1011 1111 128.0.0.1 - 191.255.255.255C类 1100 0000 - 1101 1111 192.0.0.1 - 223.255.255.255D类 1110 0000 - 1110 1111 224.0.0.1 - 239.255.255.255 表示组播地址(多投点数据传送)E类 1111 0000 - 1111 1111 240.0.0.1 - 255.x.x.x 属于保留测试
127ip地址是保留回环地址,使用保留地址的网络只能在内部进行通信,而不能与其他网络互连。
1.5 域名
因特网客户端和服务器相互通信时使用的是IP地址,后来因特网定义了一组人性化的机制,将域名映射到IP地址,域名是一串用局点分割的单纯(字母、数组、破折号)。这个映射通过一个分布
世界范围内的数据库(DNS,域名系统)来管理,其每条定义了一组域名和一组IP地址之间的映射。每台主机都有本地定义的域名俗称 localhost ,这个域名总是映射回送地址127.0.0.1。
一个域名可以和一个ip映射。
多个域名可以映射同一个IP
多个域名可以映射同一组的多个IP
1.6 端口
客户端和服务器通过在连接上发送和接收字节流来通信,从连接一对进程来说是点对点的,从数据同时可以双向流动来说是全双工的,并且发出去的字符和接收到的字符来说是可靠的。
一个套接字连接的是一个端点,每个套接字都有相应的套接字地址,是由一个因特网地址和一个16位整数端口组成的,用 “端口” 来表示。
当客户端发起一个请求时,客服端的端口是由内核自动分配的,称为临时端口,而服务器的端口通常是已分配好的,例如web端口是80,email是25,我们可以在/etc/services来查到。
通俗点讲为了区分一台主机接收到的数据包应该转交给哪个进程来进行处理,使用端口号来区别。
1.7 字节序
字节序是一个处理器架构的特性,用于指示像整数这样的大数据内部的字节如何排序,同一台计算机内进程通信不需要关心这个问题。字节序分为大端序和小端序。
大端序:最大字节地址出现在最低有效字节上
小端序:最小字节地址出现在最低有效字节上
例如一个值:0x04030201,如果用一个指针 p 指向这个值,那么小端 p[0]存储1,p[3]存储4,大端相反。
1.8 套接字
套接字是通信端点的抽象,如同文件描述符一样。用来创建网络应用,
流式套接字(SOCK_STREAM) TCP
提供了一个面向连接、可靠的数据传输服务,数据无差错、无重复的发送且按发送顺序接收。内设置流量控制,避免数据流淹没慢的接收方。
数据被看作是字节流,无长度限制。
数据报套接字(SOCK_DGRAM) UDP提供无连接服务。数据包以独立数据包的形式被发送,不提供无差错保证,数据可能丢失或重复,顺序发送,可能乱序接收。原始套接字(SOCK_RAW)可以对较低层次协议如IP、ICMP直接访问。使用这个套接字需要程序字节构造协议头部,同时需要超级用户权限。
1.9 UDP和TCP
相同点:
同为传输层协议
不同点:
tcp:有连接,保证可靠
udp:无连接,不保证可靠
TCP(即传输控制协议)
是一种面向连接的传输层协议,它能提供高可靠性通信(即数据无误、
数据无丢失、数据无失序、数据无重复到达的通信)
适用情况:
适合于对传输质量要求较高,以及传输大量数据的通信。
在需要可靠数据传输的场合,通常使用TCP协议
MSN/QQ等即时通讯软件的用户登录账户管理相关的功能通常采用TCP协议
应用: SMTP 电子邮件
TELNET 远程终端接入
HTTP 万维网
FTP 文件传输
UDP(User Datagram Protocol)用户数据报协议
是不可靠的无连接的协议。在数据发送前,因为不需要进行连接,所以
可以进行高效率的数据传输。
适用情况:
发送小尺寸数据(如对DNS服务器进行IP地址查询时)
在接收到数据,给出应答较困难的网络中使用UDP。(如:无线网络)
适合于广播/组播式通信中。
MSN/QQ/Skype等即时通讯软件的点对点文本通讯以及音视频通讯通常采用UDP协议
流媒体、VOD、VoIP、IPTV等网络多媒体服务中通常采用UDP方式进行实时数据传输
应用: DNS 域名转换
TFTP 文件传输
SNMP 网络管理
NFS 远程文件服务器
1.10 网络编程流程
TCP:
服务器:
创建套接字 socket( )
填充服务器网络信息结构体 sockaddr_in
将套接字与服务器网络信息结构体绑定 bind( ) 固定自己的信息
将套接字设置为被动监听状态 listen( )
阻塞等待客户端的连接请求 accept( )
进行通信 接收数据recv( )/发送数据 send( )
关闭套接字close()
客户端:
创建套接字 socket( )
填充服务器网络信息结构体 sockaddr_in
发送客户端连接请求 connect( )
进行通信 发送数据 send( )/接收数据recv( )
关闭套接字close()
UDP:
服务器
创建套接字 socket( )
填充服务器网络信息结构体 sockaddr_in
将套接字与服务器网络信息结构体绑定 bind( )
进行通信 recvfrom( )/sendto( )
客户端
创建套接字 socket( )
填充服务器网络信息结构体 sockaddr_in
进行通信 sendto( )/recvfrom( )
二、网络编程函数
2.1 创建套接字
#include <sys/types.h> /* See NOTES */#include <sys/socket.h>int socket(int domain, int type, int protocol);功能:创建一个套接字参数:domain:通信域,协议族AF_UNIX:本地通信AF_INET:ipv4网络协议AF_INET6:ipv6网络协议AF_PACKET:底层通信协议type:SOCK_STREAM:流式套接字 tcp(面向连接的套接字)SOCK_DGRAM:数据报套接字 UDP (无连接的套接字)SOCK_RAW:原始套接字SOCK_SEQPACKET:固定长度的、有序的、可靠的、面向连接的报文传递。protocol:协议,一般为0如果需要其他协议:IPPROTO_IP:ipv4网际协议IPPROTO_IPV6:ipv6网际协议IPPROTO_ICMP:因特网控制报文协议IPPROTO_RAW:原始IP数据包协议IPPROTO_TCP:传输控制协议IPPROTO_UDP:用户数据报协议 返回值:成功:文件描述符失败:-1
注释:对于数据报(SOCK_DGRAM),两个对等进程之间通信时不需要建立逻辑连接,只需要向对等进程的套接字发送报文。
字节流(SOCK_STREAM),在交换数据前需要建立连接。
socket套接字与open相似,使用完之后直接用close关闭即可。
套机制通信是双向的,我们可以使用shutdown函数来禁止一个套接字的I/O。
#include <sys/socket.h>int shutdown(int sockfd, int how);参数:sockfd:socket函数返回的fdhow:SHUT_RD:关闭读端:无法从套接字读数据SHUT_WR:关闭写端:无法向套接字写数据SHUT_RDWR:关闭读写端:既无法读又无法写。
设置允许重用本地地址和端口(最好加上)
int on = 1;if (0 > setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on))){perror("setsockopt");return -1;}
对于无连接的套接字(UDP),数据包到达时可能已经没有了次序,因此如果不能将所有数据放在一个数据包中,则应用程序必须关系数据包的次序,另外无连接的套接字数据包可能会丢失,所以
如果不可以容忍丢失数据就使用面向连接的套接字(tcp)
2.2 字节序转换
一般Linux系统采用小端序,TCP/IP协议采用大端序。使用下面四个函数来处理处理器字节序和网络字节序之间的转换。
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong);返回值:以网络字节序表示的32位整数uint16_t htons(uint16_t hostshort);返回值:以网络字节序表示的16位整数uint32_t ntohl(uint32_t netlong);返回值:以主机字节序表示的32位整数uint16_t ntohs(uint16_t netshort);返回值:以主机字节序表示的16位整数区分:h:主机n:网络l:长(4字节)整数s:短(2字节)整数#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>int inet_aton(const char *cp, struct in_addr *inp);功能;j将cp字符串转换成32位网络字节序二进制值存放在inp返回值:失败 0 成功 非0 in_addr_t inet_addr(const char *cp);功能:同上,返回转换后的地址in_addr_t inet_network(const char *cp);char *inet_ntoa(struct in_addr in);功能:将32位网络字节序二进制地址转换为字符串struct in_addr inet_makeaddr(int net, int host);in_addr_t inet_lnaof(struct in_addr in);in_addr_t inet_netof(struct in_addr in);
2.3 关联套接字与地址
对于服务端需要给一个接收客户端请求的服务器套接字关联一个地址,客户端应有一种方法来发现连接服务器所需的地址,最简单办法就是服务器保留一个地址并注册在/etc/services中。
#include <sys/types.h> /* See NOTES */#include <sys/socket.h>int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);功能:将套接字et与网络信息结构体绑定参数:sockfd:文件描述符,socket的返回值addr:网络信息结构体通用的(一般不用)struct sockaddr {sa_family_t sa_family; 2 byteschar sa_data[14]; 14 bytes}网络信息结构体:sockaddr_in#include <netinet/in.h>struct sockaddr_in { u_short sin_family; // 地址族, AF_INET,2 bytesu_short sin_port; // 端口,2 bytes htons(9999);struct in_addr sin_addr; ==>struct in_addr{in_addr_t s_addr; // IPV4地址,4 bytes inet_addr("172.16.6.123");};char sin_zero[8]; // 8 bytes unused,作为填充}; addrlen:addr的长度返回值:成功:0失败:-1
用法:
struct sockaddr_in serveraddr;serveraddr.sin_family = AF_INET;serveraddr.sin_port = htons(8888);serveraddr.sin_addr.s_addr = inet_addr("127.0.0.1");bind(sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));
使用限制:
在进程正在运行的计算机上,指定的地址必须有效,不能指定一个其他机器的地址
地址必须和创建的套接字时的地址族所支持的格式相匹配
地址中端口号不得小于1024,
一个套接字端点只能绑定到一个地址
查询绑定到套接字上的地址#include <sys/socket.h>int getsockname(int sockfd, struct sockaddr *addr, socklen_t *addrlen);如果已经建立连接使用下面函数查询对方地址int getpeername(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
2.4 建立连接
如果要处理一个面向连接(tcp)的网络服务,那么在开始数据交换前,需要在请求服务的套接字(客户端)和提供服务的进程套接字(服务端)之间建立连接。
#include <sys/types.h> /* See NOTES */#include <sys/socket.h>int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);功能:发送客户端的连接请求参数:sockfd:文件描述符,socket的返回值addr:自己填充的服务器的网络信息结构体,必须与服务器保持一致addrlen:addr的长度返回值:成功:0失败:-1
当尝试连接服务器时,可能会因为一些原因导致连接失败,如果需要链接成功,要保证连接的计算机必须是开启并且运行的,服务器必须绑定到一个与之向连接的地址上,并且服务器等待队列有空间。
connect还用于无连接网络服务(UDP)
2.5 建立监听
#include <sys/types.h> /* See NOTES */#include <sys/socket.h>int listen(int sockfd, int backlog);功能:将套接字设置为被动监听的状态参数:sockfd:文件描述符,socket的返回值backlog:允许同时响应客户端连接请求的个数,最大128,一旦满了就拒绝多余请求,返回值:成功:0失败:-1
2.6 建立连接
#include <sys/types.h> /* See NOTES */#include <sys/socket.h>int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);功能:阻塞等待客户端的连接请求参数:sockfd:文件描述符,socket的返回值addr:获取到的客户端的网络信息结构体addrlen:接收到客户端的addr的长度当不需要知道客户端地址时, addr 和addrlen 可设置为NULL返回值:成功:新的文件描述符(用于通信)失败:-1例子:int acceptfd;struct sockaddr_in clientaddr;acceptfd = accept(sockfd, (struct sockaddr *)&clientaddr, &addrlen);
注释:函数返回的文件描述符是套接字描述符,该描述符连接到调用connect的客户端,与原始套接字描述符具有相同的类型和地址族,原始的套接字将继续保持可用状态并接收其他连接请求。
如果没有连接请求accept将会一直阻塞直到请求来。当然服务器可以使用select和poll来等待请求,这样效率更高,我们下面会说。
2.7 数据传输
2.7.1 发送数据
当发送函数成功返回时并不意味着另一端接收到了数据,只是保证数据已经发送到了网络驱动程序。
#include <sys/types.h>#include <sys/socket.h>ssize_t send(int sockfd, const void *buf, size_t len, int flags);功能:发送数据参数:sockfd:文件描述符服务器:accept的返回值acceptfd客户端:socket的返回值sockfdbuf:发送的数据len:buf的长度flags: 一般为0MSG_CONFIRM:提供链路层反馈以保持地址映射有效。MSG_DONTROUTE:勿将数据包路由出本地网络MSG_DONTWAIT:允许非阻塞操作MSG_EOR:发送数据后关闭套接字的发送端MSG_MORE:延迟发送数据包允许写更多数据MSG_NOSIGNAL:在写无连接的套接字时不产生SIGPIPE信号MSG_OOB:如果协议支持,发送外带数据,返回值:成功:发送的数据的长度失败:-1ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);功能:与send函数一样,但是该函数可以在无连接套接字上指定一个目标地址,常用语udp参数:socket:文件描述符message:发送的数据length:数据的长度flags:标志位,一般为0dest_addr:目的地址(需要知道给谁发送)dest_len:addr的长度返回值:成功:发送的数据的长度失败:-1ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);功能:通过带有msghdr结构体里指定多重缓冲区传输数据struct msghdr {void *msg_name; /* optional address */socklen_t msg_namelen; /* size of address */struct iovec *msg_iov; /* scatter/gather array */size_t msg_iovlen; /* # elements in msg_iov */void *msg_control; /* ancillary data, see below */size_t msg_controllen; /* ancillary data buffer len */int msg_flags; /* flags on received message */};
2.7.2 接收数据
#include <sys/types.h>#include <sys/socket.h>ssize_t recv(int sockfd, void *buf, size_t len, int flags);功能:接收数据参数:sockfd:文件描述符服务器:accept的返回值acceptfd客户端:socket的返回值sockfdbuf:接收的数据len:buf的长度flags: 一般为0阻塞 MSG_CMSG_CLOEXEC:套接字上接收的文件描述符设置执行时关闭的标准MSG_DONTWAIT:非阻塞MSG_ERRQUEUE:接收错误信息作为辅助数据MSG_OOB:获取外带数据MSG_PEEK:返回数据包内容而不真正取走数据包MSG_TRUNC:即使数据包被截断,也返回数据包实际长度MSG_WAITALL:等待直到所有数据可用返回值:成功:接收的数据的长度失败:-10:发送端关闭文件描述符ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);功能:接收数据参数:sockfd:文件描述符buf:接收的数据len:buf 的长度flags:标志位,一般为0src_addr:源的地址(接收者的信息,自动填充)addrlen:addr的长度返回值:成功:接收的数据的字节数失败:-1ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);功能:将接收到的数据放入多个缓冲区
2.8 带外数据
带外数据是一些通信协议所支持的可选功能,与普通数据相比它允许更高优先级的数据传输,tcp支持带外数据,udp不支持。tcp将带外数据成为紧急数据,而且仅支持一个字节的紧急数据。
2.9 TCP demo
服务端
#include<stdio.h>#include<stdlib.h>#include<sys/types.h>#include<sys/socket.h>#include<netinet/in.h>#include<unistd.h>#include<string.h>#include<pthread.h>#include<semaphore.h>#define N 512sem_t sem_r;sem_t sem_w;char buf[N];int accept_fd;int ret;void *handler_A(void *arg){sem_wait(&sem_r);while(1) {memset(&buf,0,sizeof(buf));ret = recv(accept_fd,buf,sizeof(buf),0);if(ret < 0) {perror("fail to read");break;}else if(ret == 0){printf("write close\n");break;}else {fputs(buf,stdout);}}sem_post(&sem_r);pthread_exit((void *)0);}void *handler_B(void *arg){sem_wait(&sem_w);while(1) {memset(&buf,0,sizeof(buf));fgets(buf,sizeof(buf),stdin);if((send(accept_fd,&buf,sizeof(buf),0)) < 0) {perror("fail to write");break;}}sem_post(&sem_r);pthread_exit((void *)0);}int main(){int socket_fd;socklen_t addrlen;struct sockaddr_in socket_addr;struct sockaddr_in clientaddr;pthread_t thread_A,thread_B;addrlen = sizeof(clientaddr);socket_fd = socket(AF_INET,SOCK_STREAM,0);if(socket_fd == -1) {perror("fail to socket");exit(1);}printf("socket............\n");memset(&socket_addr,0,sizeof(socket_addr));socket_addr.sin_family = AF_INET;socket_addr.sin_port = htons(8878);socket_addr.sin_addr.s_addr = inet_addr("192.168.1.22");if((bind(socket_fd,(struct sockaddr *)&socket_addr,sizeof(socket_addr))) == -1) {perror("fail to bind");exit(1);}printf("bind............\n");if((listen(socket_fd,5)) == -1) {perror("fail to listen");exit(1);}printf("listen..........\n");accept_fd = accept(socket_fd,(struct sockaddr*)&clientaddr,&addrlen);printf("accept:1ip %s port = %hu\n",inet_ntoa(clientaddr.sin_addr),ntohs(clientaddr.sin_port));if(sem_init(&sem_r,0,1) < 0) {perror("fail to sem_init");exit(1);}if(sem_init(&sem_w,0,1) < 0) {perror("fail to sem_init");exit(1);}if(pthread_create(&thread_A,NULL,handler_A,NULL) != 0) {perror("fail to pthread_create");exit(1);}if(pthread_create(&thread_B,NULL,handler_B,NULL) != 0) {perror("fail to pthread_create");exit(1);}pthread_join(thread_A,NULL);pthread_join(thread_B,NULL);sem_destroy(&sem_r);sem_destroy(&sem_w);close(socket_fd);close(accept_fd);}
客户端
#include<stdio.h>#include<stdlib.h>#include<sys/types.h>#include<sys/socket.h>#include<netinet/in.h>#include<unistd.h>#include<string.h>#include<pthread.h>#include<semaphore.h>#define N 512char buf[N];sem_t sem_r;sem_t sem_w;int socket_fd;int ret;void *handler_A(void *arg){sem_wait(&sem_r);while(1) {memset(&buf,0,sizeof(buf));ret = recv(socket_fd,buf,sizeof(buf),0);if(ret < 0) {perror("fail to read");break;}else if(ret == 0){printf("write close\n");break;}else {fputs(buf,stdout);}}sem_post(&sem_r);pthread_exit((void *)0);}void *handler_B(void *arg){sem_wait(&sem_w);while(1) {memset(&buf,0,sizeof(buf));fgets(buf,sizeof(buf),stdin);// value = p->data;if((send(socket_fd,buf,sizeof(buf),0)) < 0) {perror("fail to write");break;}}sem_post(&sem_r);pthread_exit((void *)0);}int main(){struct sockaddr_in connect_fd;pthread_t thread_A,thread_B;socket_fd = socket(AF_INET,SOCK_STREAM,0);if(socket_fd == -1) {perror("fail to socket");exit(1);}printf("socket.............\n");memset(&connect_fd,0,sizeof(connect_fd));connect_fd.sin_family = AF_INET;connect_fd.sin_port = htons(8878);connect_fd.sin_addr.s_addr = inet_addr("192.168.1.22");if((connect(socket_fd,(struct sockaddr *)&connect_fd,sizeof(connect_fd))) == -1) {perror("fail to connect");exit(1);}printf("connect............\n");if(sem_init(&sem_r,0,1) < 0) {perror("fail to sem_init");exit(1);}if(sem_init(&sem_w,0,1) < 0) {perror("fail to sem_init");exit(1);}if(pthread_create(&thread_A,NULL,handler_A,NULL) != 0) {perror("fail to pthread_create");exit(1);}if(pthread_create(&thread_B,NULL,handler_B,NULL) != 0) {perror("fail to pthread_create");exit(1);}pthread_join(thread_A,NULL);pthread_join(thread_B,NULL);sem_destroy(&sem_r);sem_destroy(&sem_w);close(socket_fd);}
2.10 UDP demo
服务端
#include <stdio.h>#include <string.h>#include <stdlib.h>#include <strings.h>#include <sys/types.h>#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>int main(){/*1. 创建套接字*/int sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0){perror("socket");return -1;}printf("socket..............\n");/*2. 绑定本机地址和端口*/struct sockaddr_in addr;memset(&addr, 0, sizeof(addr));addr.sin_family = AF_INET;addr.sin_port = htons(8888);addr.sin_addr.s_addr = htonl(INADDR_ANY);if (0 > bind(sockfd, (struct sockaddr*)&addr, \sizeof(addr))){perror("bind");return -1;}printf("bind..............\n");/*3. 数据接收*/struct sockaddr_in cliaddr;socklen_t addrlen = sizeof(cliaddr);char buf[1024];int ret;while (1){ret = recvfrom(sockfd, buf, sizeof(buf), \0, (struct sockaddr*)&cliaddr, &addrlen);if (0 > ret){perror("recvfrom");break;}printf("recv: ");fputs(buf, stdout);if (0 > sendto(sockfd, buf, sizeof(buf), 0, \(struct sockaddr*)&cliaddr, addrlen)){perror("sendto");break;}}/*4. 关闭套接字*/close(sockfd);return 0;}
客服端
#include <stdio.h>#include <string.h>#include <stdlib.h>#include <strings.h>#include <sys/types.h>#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>int main(int argc, const char *argv[]){if (argc < 2){fprintf(stderr, "Usage: %s <srv_ip>\n", argv[0]);return -1;}/*1. 创建套接字*/int sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0){perror("socket");return -1;}printf("socket..............\n");struct sockaddr_in addr;memset(&addr, 0, sizeof(addr));addr.sin_family = AF_INET;addr.sin_port = htons(8888);addr.sin_addr.s_addr = inet_addr(argv[1]);/*3. 数据接收*/char buf[1024];int ret;while (1){printf("send: ");fgets(buf, sizeof(buf), stdin);ret = sendto(sockfd, buf, sizeof(buf), 0, \(struct sockaddr*)&addr, sizeof(addr));if (0 > ret){perror("recvfrom");break;}ret = recvfrom(sockfd, buf, sizeof(buf), \0, NULL, NULL);if (0 > ret){perror("recvfrom");break;}printf("recv: ");fputs(buf, stdout);}/*4. 关闭套接字*/close(sockfd);return 0;}
网络高级编程
三、I/O模型
3.1 I/O模型
- 阻塞IO:常用、简单、效率低
- 非阻塞IO:可防止进程阻塞在IO操作上,需要轮询,极其消费CPU
- IO多路复用:允许同时多个IO进行控制(重点)
- 信号驱动IO:一种异步通信方式
3.2 IO多路复用
为什么使用I/O多路复用?
如果应用程序处理多路输入输出流,采用阻塞模式的话效率将极其的低,如果采用非阻塞,那么将一直轮询,CPU占用率又会非常高,所以使用I/O多路IO多路复用基本思想
先构造一张有关描述符的表,然后调用一个函数,当这些文件描述符中的一个或多个已准备好进行IO时函数才返回,函数返回时告诉进程已经有描述符就绪,可以进行IO操作。实现函数select
select函数可以使我们执行I/O多路转接,通过传给select函数的参数可以告诉内核:
a.我们所关心的描述符
b.对于每个描述符我们所关心的条件,是否想从一个给定描述符读/写,是否关心描述符异常
c.愿意等待多长时间
也可以通过返回值得到以下信息
a.已经准备好的文件描述符
b. 对于读、写、异常者三个条件中每一个,哪些已经准备好
然后我们就可以使用read和write函数读写。#include<sys/time.h>#include<sys/types.h>#include<unistd.h>int select(int nfds,fd_set *read_fds,fd_set *write_fds,fd_set *except_fds,struct timeval *timeout);参数: nfds 所有监控文件描述符最大的那一个 +1.(因为文件描述符编号从0开始,所以要加1)read_fds 所有可读的文件描述符集合。 没有则为NULLwrite_fds 所有可写的文件描述符集合。 没有则为NULLexcept_fds 处于异常条件的文件描述符 没有则为NULLtimeval: 超时设置。 NULL:一直阻塞,直到有文件描述符就绪或出错0 :仅仅监测文件描述符集的状态,然后立即返回非0 :在指定时间内,如果没有事件发生,则超时返回返回值:当timeval设置为NULL:返回值 -1 表示出错>0 表示集合中有多少个描述符准备好当设置timeval非0时: 返回值 -1:表示出错>0: 表示集合中有多少描述符准备好=0: 表示时间到了还没有描述符准备好对于fd_set数据类型有以下四种处理方式 fd:文件描述符、 fdset文件描述符集合void FD_SET(int fd,fd_set *fdset): 将fd加入到fdest void FD_CLR(int fd,fd_set *fdest): 将fd从fdest里面清除void FD_ZERO(fd_set *fdest): 从fdest中清除所有文件描述符void FD_ISSET(int fd,fd_set *fdest):判断fd是否在fdest集合中这些接口实现为宏或者函数,调用 FD_ZERO 将fd_set变量的所有位置设置为0,如果要开启描述符集合的某一位,可以调用 FD_SET ,调用FD_CLR 可以清除某一位,FD_ISSET用来检测某一位是否打开。在申明了一个描述符集合之后,必须使用FD_ZERO将其清零,下面是使用操作:fd_set reset;int fd;FD_ZERO(&reset);FD_SET(fd, &reset);FD_ZERO(STDIN_FILENO, &reset);if (FD_ISSET(fd, &reset)) {}
对于“准备好” 这个词这里说明一下,什么才是准备好,什么是没有准备好,如果对读集(read_fds/write_fds) 中的一个描述符进行read/write操作没有阻塞则认为是准备好,或者对except_fds有一个未决异常条件,则认为准备好。
一个描述符的阻塞并不影响整个select的阻塞。当文件描述符读到文件结尾时候,read返回0.
4. 实现函数poll
poll函数与select函数相似,不同的是,poll不是为每个条件(读、写、异常)构造一个文件描述符,而是构造一个pollfd结构数组,每个数组元素指定一个描述符编号,poll函数可以用于任何类型的文件描述符。
#include <poll.h>int poll(struct pollfd *fds, nfds_t nfds, int timeout);参数:fds:pollfd结构数组struct pollfd {int fd; /* 文件描述符 */short events; /* 请求事件 */short revents; /* 返回事件 */};events:需要将events设置为以下一个或者多个值,这些值会告诉内核哪些是我们关系的文件描述符POLLIN 不阻塞地读高优先级数据意外的数据POLLRDNORM 不阻塞地读普通数据POLLRDBAND 不阻塞地读优先级数据POLLPRI 不阻塞地读高优先级数据POLLOUT 普不阻塞地读写普通数据POLLWRNORM 同POLLOUTPOLLWRBAND 不阻塞地写低优先级数据POLLERR 发生错误POLLHUP 发生挂起(当挂起后就不可以再写该描述符,但是可以读)POLLNVAL 描述字不是一个打开的文件revents:返回的文件描述符,用于说明描述符发生了哪些事件。nfds:数组中元素数timeout:等待时间= -1:永远等待,直到有一个描述符准备好,或者捕捉到一个信号,如果捕捉到信号返回-1。= 0 :不等待,立即返回。这是轮询的方法。> 0: 等待的毫秒数,有文件描述符准备好或者timeout超时立即返回。超时返回值为0.
5. 应用demo
select 程序#include <stdio.h>#include <string.h>#include <stdlib.h>#include <strings.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <sys/time.h>#include <unistd.h>#include <sys/select.h>int main(){int fd = open("/dev/input/mouse1", O_RDONLY);if (fd < 0){perror("open");return -1;}fd_set fds, rfds;FD_ZERO(&fds); //清空集合FD_SET(0, &fds); //把键盘加入集合中FD_SET(fd, &fds); //把鼠标加入集合中int retval;while (1){rfds = fds;struct timeval tv = {1, 0};//retval = select(fd+1, &rfds, NULL, NULL, NULL);retval = select(fd+1, &rfds, NULL, NULL, &tv);if (retval < 0){perror("select");break;}else if (0 == retval){printf("timeout..........\n");continue;}char buf[1024];if (FD_ISSET(0, &rfds)) //判断是否是键盘产生事件{read(0, buf, sizeof(buf));printf("Data from Keyboard!\n");}if (FD_ISSET(fd, &rfds)){read(fd, buf, sizeof(buf));printf("Data from Mouse!\n");}}close(fd);}poll 程序 #include <stdio.h>#include <string.h>#include <stdlib.h>#include <strings.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <sys/time.h>#include <unistd.h>#include <sys/select.h>#include <poll.h>int main(){int fd = open("/dev/input/mouse1", O_RDONLY);if (fd < 0){perror("open");return -1;}#if 0fd_set fds, rfds;FD_ZERO(&fds); //清空集合FD_SET(0, &fds); //把键盘加入集合中FD_SET(fd, &fds); //把鼠标加入集合中#elsestruct pollfd fds[2];fds[0].fd = 0; //键盘fds[0].events = POLLIN; //请求读fds[1].fd = fd; //鼠标fds[1].events = POLLIN; //请求读#endifint retval;while (1){retval = poll(fds, 2, 1000);if (retval < 0){perror("poll");break;}else if (0 == retval){printf("timeout..........\n");continue;}char buf[1024];if (fds[0].revents == POLLIN) //判断是否是键盘产生事件{read(0, buf, sizeof(buf));printf("Data from Keyboard!\n");}if (fds[1].revents == POLLIN){read(fd, buf, sizeof(buf));printf("Data from Mouse!\n");}}close(fd);}
四、服务器模型
在网络中,通常都是一个服务器多个客户端,为了处理每个客户端不同的请求,服务器程序必须有不同的处理程序,所以就有了服务器模型,常用的服务器模型有两种:
循环服务器:同一时刻只可以处理一个客户请求
并发服务器:同一时刻可以处理多个客户请求
4.1. 循环服务器模型
服务器运行后等待客户端连接,当接收到一个客户连接后就开始处理请求,处理完之后断开连接。一次只可以处理一个客户端请求,只有处理完当前客户端请求才可以处理下一个客户端请求,如果
一个客户端一直占有则其他客户端将无法连接,所以一般很少用。
socket(...); bind(...); listen(...); while(1) { accept(...); while(1) { recv(...); process(...); send(...); } close(...); }
4.2. 并发服务器
并发服务器的设计思想服务器接收客户端连接请求后创建子进程来处理客户端服务,但是过多的客户端连接就会有多个子进程,这也会影响服务器效率。
流程如下:
void handler(int sigo){while (waitpid(-1, NULL, WNOHANG) > 0); //一个SIGCHLD可能对应多个僵尸进程,循环收尸}int sockfd = socket(...); bind(...); listen(...);signal(SIGCHLD, handler); //注册SIGCHLD信号,当产生SIGCHLD信号时调用handler函数while(1) { int connfd = accept(...); if (fork() = = 0) { close(sockfd);while(1) { recv(...); process(...); send(...); }close(connfd); exit(...); } close(connfd); }
五、套接字属性设置与获取
套接字机制提供了两个套接字接口来控制套接字行为,一个用来获取一个用来查询,可以设置/获取以下三种选项
通用选项,工作在所有套接字类型上
在套接字层次管理的选项,但是依赖下层协议支持
特定于某种协议的选项,每个协议独有的。
#include <sys/types.h> /* See NOTES */#include <sys/socket.h>int getsockopt(int sockfd, int level, int optname,void *optval, socklen_t *optlen);功能:查看当前选项值参数:同setsockopt参数返回值:成功0,失败-1 int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen);功能:设置套接字选项参数: sockfd:套接字level指定控制套接字的层次.可以取三种值: 1)SOL_SOCKET:通用套接字选项. 2)IPPROTO_IP:IP选项. 3)IPPROTO_TCP:TCP选项. optname:以下值SO_DEBUG 如果*optval非0,则启动网络驱动调试功能SO_REUSEADDR 如果*optval非0,则重用bind中地址SO_TYPE 标识套接字类型(仅getsockopt)SO_ERROR 返回挂起套接字错误并清除SO_DONTROUTE 如果*optval非0,绕过通常路由SO_BROADCAST 如果*optval非0,广播数据报SO_SNDBUF 发送缓冲区的字节长度SO_SNDTIMEO 套接字发送调用超时值SO_RCVBUF 接收缓冲区的字节长度SO_RCVTIMEO 套接字接收调用超时值SO_KEEPALIVE 如果*optval非0,启用周期性keep-alive报文SO_OOBINLINE 如果*optval非0,将带外数据放在普通数据中 SO_LINGER 当有未发送报文而套接字关闭时,延迟时间 optval:0:禁止选项1:开启选项optlen:指向optval参数的大小返回值:成功0,失败-1 设置超时监测:1.struct timeval tv = {1, 0};if (0 > setsockopt(connfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv))){perror("setsockopt");return -1;} 2. struct timeval tv = {5, 0};select(fd+1, &rfds, NULL, NULL, &tv);
六、广播和组播
上面所说的都属于单播,如果发给局域网中所有主机则为广播,只有UDP协议才可以使用广播。
因为广播可以发送给所有主机,过多广播会占用大量网络宽带,所以组播是将一些主机加入到一个多播组,只有该组的主机可以接收。
6.1 广播发送流程
- 创建用户数据报套接字
- 缺省创建套接字不允许广播数据包,需要aetsockopt设置(SO_BROADCAST)属性
- 接收方地址定位广播地址
- 指定端口信息
- 发送数据包
6.2 广播接收
- 创建用户数据报套接字
- 绑定IP地址(广播IP或者0.0.0.0)和端口号
- 等待接收数据
6.3 组播发送流程
- 创建用户数据报套接字
- 接收方地址定位组播地址
- 指定端口信息
- 发送数据包
6.4 组播接收流程
- 创建用户数据报套接字
- 加入多播
- 绑定IP地址(广播IP或者0.0.0.0)和端口号
- 等待接收数据
5.5 广播demo
发送数据
#include <stdio.h>#include <string.h>#include <stdlib.h>#include <strings.h>#include <sys/types.h>#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>int main(int argc, const char *argv[]){/*1. 创建数据报套接字*/int sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0){perror("socket");return -1;}printf("socket..............\n");/*2. 设置允许发送广播包*/int on = 1;if (0 > setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on))){perror("setsockopt");return -1;}/*3. 指定接收方的地址为广播地址,以及端口号*/struct sockaddr_in addr;memset(&addr, 0, sizeof(addr));addr.sin_family = AF_INET;addr.sin_port = htons(8888);addr.sin_addr.s_addr = inet_addr("192.168.3.255");/*4. 发送数据*/char buf[1024];int ret;while (1){printf("send: ");fgets(buf, sizeof(buf), stdin);ret = sendto(sockfd, buf, sizeof(buf), 0, \(struct sockaddr*)&addr, sizeof(addr));if (0 > ret){perror("recvfrom");break;}}/*4. 关闭套接字*/close(sockfd);return 0;}
接收数据
#include <stdio.h>#include <string.h>#include <stdlib.h>#include <strings.h>#include <sys/types.h>#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>int main(){/*1. 创建套接字*/int sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0){perror("socket");return -1;}printf("socket..............\n");int on = 1;setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));/*2. 绑定广播地址和端口*/struct sockaddr_in addr;memset(&addr, 0, sizeof(addr));addr.sin_family = AF_INET;addr.sin_port = htons(8888);addr.sin_addr.s_addr = inet_addr("192.168.3.255");if (0 > bind(sockfd, (struct sockaddr*)&addr, \sizeof(addr))){perror("bind");return -1;}printf("bind..............\n");/*3. 数据接收*/struct sockaddr_in cliaddr;socklen_t addrlen = sizeof(cliaddr);char buf[1024];int ret;while (1){ret = recvfrom(sockfd, buf, sizeof(buf), \0, (struct sockaddr*)&cliaddr, &addrlen);if (0 > ret){perror("recvfrom");break;}printf("recv: ");fputs(buf, stdout);}/*4. 关闭套接字*/close(sockfd);return 0;}
六、扩展函数
6.1 获取/设置主机名称
#include <unistd.h>int gethostname(char *name, size_t len);int sethostname(const char *name, size_t len);
6.2 获取与套接字相连的远程协议地址
#include <sys/socket.h>int getpeername(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
6.3 获取本地套接字接口协议地址
#include <sys/socket.h>int getsockname(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
6.4 根据主机名获取主机信息
#include <netdb.h>struct hostent *gethostbyname(const char *name);
6.5 根据主机地址获取主机地址
#include <sys/socket.h> /* for AF_INET */struct hostent *gethostbyaddr(const void *addr, socklen_t len, int type);
6.6 根据主机协议名获取主机协议信息
#include <netdb.h>struct protoent *getprotobyname(const char *name);
6.7 根据协议号获取主机协议信息
#include <netdb.h>struct protoent *getprotobynumber(int proto);
Linux操作系统下C语言网络编程(全文23475字,包含了Linux系统下所有网络编程的知识点,附程序代码)相关推荐
- 智能窗帘传感器c语言程序,基于单片机的智能窗帘控制系统设计(附程序代码)
基于单片机的智能窗帘控制系统设计(附程序代码)(论文18000字,程序代码) 摘要:二十一世纪初以来,科学技术不断发展,智能家居涌现于各家各户,人们越来越重视生活质量的提高.但是传统的手动开合窗帘耗时 ...
- 基于智能家居c语言程序代码,基于单片机的智能家居系统设计(附程序代码)
基于单片机的智能家居系统设计(附程序代码)(任务书,开题报告,外文翻译,论文10000字) 摘要 基于近年来通信电子技术的高速发展,使得一些原来可望不可及的事关民生的技术变为可能,条件允许的情况下,人 ...
- linux系统引导设置,Linux操作系统GRUB引导程序配置方法大全 - 技术文档 - 新手入门 Linux时代......
1. GRUB 介绍 计算机在启动的时候,首先由BIOS中的程序执行自检,自检通过后,就根据CMOS 的配置找到第一个可启动磁盘的MBR中的Boot Loader程序(一般在启动盘的第一个物理扇区,占 ...
- linux操作系统基础与实训教程,清华大学出版社-图书详情-《Linux操作系统基础与实训教程》...
前 言 随着计算机技术的不断发展,越来越多的用户认识到Linux的优点.作为唯一一款与微软Windows竞争的桌面操作系统,Linux逐渐受到用户的重视:并且随着其在市场中占有量的稳步提高,已经有越来 ...
- win7网络感叹号dns服务器未响应,笔记本win7系统下无线网络显示已连接却不能上网有感叹号如何解决...
在笔记本中通常都内置有无线网卡,可以让用户们连接无线网络来使用,但是最近有不少笔记本win7系统用户到本站反馈说无线网络显示已连接,却不能上网,而且无线网络显示有感叹号,该怎么办呢,本教程就给大家讲解 ...
- linux操作系统适合作网络服务器的基本平台工作,WindowsNT和UNIX或Linux操作系统均适合作网络服务器的基本平台工作。()...
WindowsNT和UNIX或Linux操作系统均适合作网络服务器的基本平台工作.() 更多相关问题 [单选] 血液透析患者在透析间期体重增长建议不超过干体重的() [单选] 放沥青热油时,操作人员应 ...
- Linux系统语言教程,Linux操作系统基础及语言基础教程-麦可网张凌华
本教程共43讲,主要讲解了计算机组成原理概述.Linux基础及操作系统框架.Shell命令机制.Linux命令类库机制及常用命令.Linux应用程序安装及卸载.Linux服务程序的安装及配置.Vi的设 ...
- 好看的linux操作系统,Deepin 20 - 外媒称它是最漂亮的Linux操作系统
BetaNews 网站的 Bian Fagioli 是一个 MacOS 用户,他认为苹果的桌面操作系统对于基本用户来说总体不错的,但对于一些要求高的人来说,它就会变得十分有限,生产力会受到负面影响,而 ...
- windows下运行python打印有颜色的字_Windows和Linux下Python输出彩色文字的方法教程...
前言 最近在项目中需要输出彩色的文字来提醒用户,以前写过,但是只能在win上面运行. 今天搜了下看有没有在win和Linux上通用的输出彩色文字的模块,结果发现没有,,于是就自己弄了一个,分享下,以后 ...
最新文章
- web接口响应时间标准_从零搭建Web应用(二)
- ArrayDeque中的取余
- v系列存储配置Linux多路径,linux下san存储多路径软件的配置
- 2013年1月18日调试触发器“表发生了变化,触发器或函数不能读它”的出现原因,以及解决方案...
- 网络安全 攻击类型_网络攻击的类型| 网络安全
- 教你怎样做个有“钱”途的測试project师
- sha 2 java加密_java中的SHA2密码哈希
- java返回ajax的请求值
- rhel linux 自动 fsck,red hat as 4 启动报错:checking filesystems fsck.ext3: bad magic number ......
- 脚本比别人的代码都多
- Python 水仙花数练习
- 火星时代Web前端开发完整版
- 程序员深度学习!mysql客户端工具免费绿色版
- 运维日志审计是什么意思?用什么工具好?
- 无线监控安ftp服务器,ftp服务器摄像头监控
- 怎样看服务器是虚拟还是物理,如何判断服务器为虚拟机还是物理真机?
- web页面中如何唤起打开APP实践
- Matlab之代数方程求解:函数方程的展开与合并
- DNS域名解析和正向解析
- java网课|面向对象的思想
热门文章
- PlantUML Themes
- 第六十二周总结——天降大任于斯人也
- 关于uniapp的下拉刷新,上拉加载的使用
- 一个在线jpg png转ICO的网站
- 关于发票的这些事儿,发票问题大汇总42个问题,15种发票不能抵扣
- 护眼宝_双显示屏_失效_win10护眼宝失效_护眼宝不能调节
- 飞机大战c++语言源代码,C++编写简易的飞机大战
- 图纸加密软件应用的领域有哪些?
- upqc matlab,统一电能质量调节器(UPQC)的分析及其控制策略的研究
- 批处理实例:图片批量重命名