文章目录
- 前言
- 1.网络通信概述
- 2.网络设备概述
- 3.应用层概述
- 4.IP地址分类(IPv4)
- 5.linux网络编程实践
- 5.1.socket编程接口介绍
- 5.2.IP地址格式转换函数实践
- 5.3.服务器和客户端程序
- 5.4.发送&接收程序
- 5.5poll多路监听
前言
文章部分来自网络,侵删
下面是应用编程整理的文章
linux应用编程基础:
1.文件IO
2.文件属性
3.获取系统信息
4.linux进程
5.linux中的信号
6.高级IO
7.linux线程
8.网络基础
网络编程章节结束了,linux应用编程基础也告一段落,偏基础,如果要继续深入还是得看些书,推荐书籍:《网络是怎样连接的》,《图解HTTP》,《图解TCP/IP》等书籍比较基础,适合入门。《TCP/IP入门经典》,《unix网络编程》强烈推荐。《tcp/ip详解》1,2,3卷大神之作。书籍链接:tcp/ip网络编程书籍
1.网络通信概述
1.1、从进程间通信说起:网络域套接字socket,网络通信其实就是位于网络中不同主机上面的2个进程之间的通信。
1.2、网络通信的层次
(1)硬件部分:网卡
(2)操作系统底层:网卡驱动
(3)操作系统API:socket接口
(4)应用层:低级(直接基于socket接口编程)
(5)应用层:高级(基于网络通信应用框架库)
(6)应用层:更高级(http、网络控件等)
1.3、OSI 7层网络模型(详见百度介绍)
(1)7层名字和顺序要记住,有时候笔试题目经常遇到。
应用层:网络服务与最终用户的一个接口。
协议有:HTTP FTP TFTP SMTP SNMP DNS TELNET HTTPS POP3 DHCP
表示层:数据的表示、安全、压缩。(在五层模型里面已经合并到了应用层)
格式有,JPEG、ASCll、EBCDIC、加密格式等 [2]
会话层:建立、管理、终止会话。(在五层模型里面已经合并到了应用层)
对应主机进程,指本地主机与远程主机正在进行的会话
传输层:定义传输数据的协议端口号,以及流控和差错校验。
协议有:TCP UDP,数据包一旦离开网卡即进入网络传输层
网络层:进行逻辑地址寻址,实现不同网络之间的路径选择。
协议有:ICMP IGMP IP(IPV4 IPV6)
数据链路层:建立逻辑连接、进行硬件地址寻址、差错校验 [3] 等功能。(由底层网络定义协议)
将比特组合成字节进而组合成帧,用MAC地址访问介质,错误发现但不能纠正。
物理层:建立、维护、断开物理连接。(由底层网络定义协议)
TCP/IP 层级模型结构,应用层之间的协议通过逐级调用传输层(Transport layer)、网络层(Network Layer)和物理数据链路层(Physical Data Link)而可以实现应用层的应用程序通信互联。
应用层需要关心应用程序的逻辑细节,而不是数据在网络中的传输活动。应用层其下三层则处理真正的通信细节。在 Internet 整个发展过程中的所有思想和着重点都以一种称为 RFC(Request For Comments)的文档格式存在。针对每一种特定的 TCP/IP 应用,有相应的 RFC [4] 文档。
一些典型的 TCP/IP 应用有 FTP、Telnet、SMTP、SNTP、REXEC、TFTP、LPD、SNMP、NFS、INETD 等。RFC 使一些基本相同的 TCP/IP 应用程序实现了标准化,从而使得不同厂家开发的应用程序可以互相通信
物理层:网卡,网线,集线器,中继器,调制解调器
数据链路层:网桥,交换机
网络层:路由器
常见的协议
DNS:域名解析协议 www.baidu.com
SNMP(Simple Network Management Protocol)网络管理协议
DHCP(Dynamic Host Configuration Protocol)动态主机配置协议,它是在TCP/IP网络上使客户机获得配置信息的协议
FTP(File Transfer Protocol)文件传输协议,它是一个标准协议,是在计算机和网络之间交换文件的最简单的方法。
TFTP(Trivial File Transfer Protocol):小文件传输协议
HTTP(Hypertext Transfer Protocol ):超文本传输协议
HTTPS(Secure Hypertext Transfer Protocol):安全超文本传输协议,它是由Netscape开发并内置于其浏览器中,用于对数据进行压缩和解压操作.
ICMP(Internet Control Message Protocol):Internet控制信息协议,互联网控制报文协议
ping ip定义消息类型有:TTL超时、地址的请求与应答、信息的请求与应答、目的地不可到达
SMTP(Simple Mail Transfer Protocol):简单邮件传送协议
TELNET Protocol:虚拟终端协议
UDP(User Datagram Protocol):用户数据报协议,它是定义用来在互连网络环境中提供包交换的计算机通信的协议
TCP(Transmission Control Protocol):传输控制协议,是一种面向连接的、可靠的、基于字节流的传输层通信协议 log转发:开启一个协议:tcp(三次握手和四次挥手)
物理层
首先,物理层并不是物理媒体本身,物理层的媒体包括电缆、光纤等。正因为物理媒体会有很多差异,所以物理层的作用正是尽可能地屏蔽这些差异,使上面的数据链路曾感觉不到这些差异。其主要任务就是确定与传输媒体的接口有关的一些特性,如机械特性、电气特性等。在这一层,数据的单位为比特(bit)。
数据链路层
数据链路层的使用的信道主要有以下两种类型:点对点信道、广播信道
数据链路层的协议有很多种,但有三个基本问题则是共同的:封装成帧、透明传输、差错检测。
封装成帧:就是在数据前后分别添加首部和尾部,这样就构成了 帧。
透明传输:用字节填充法(在非帧边界的控制字符插入转义字符)解决透明传输的问题。
差错检测:传输过程中可能会出现差错(比特差错),为保证可靠性,在数据链路层广泛使用了循环冗余检验CRC的检错技术
数据链路层的协议的代表包括PPP STP SDLC等,这一层的单位是帧。
网络层
网络层的任务就是选择合适的网间路由和交换结点,数据包是包含在帧中,包中封装了网络层包头,如源站点、目的站点信息等,如果你还在讨论一个ip地址,那么你是在这层的问题。此外还有一些路由协议的地址解析协议(ARP),网络层还可以实现拥塞控制、网际互连等,这一层,数据的单位为数据包。网络协议代表有IP IPX RIP OSPF 等
传输层(运输层)
传输层是整个网络体系结构中的关键层次之一。IP数据报中的首部明确标记了两个主机的IP地址,但是“两个主机之间的通信”说法不清楚,因为真正进行通信的实体是主机中的进程。根据应用程序的不用需求,运输层需要两种不同的运输协议,即面向连接的TCP和无连接的UDP。TCP数据单元为段 而UDP中数据单元为数据报。
TCP 面向连接 全双工 面向字节流 每一条TCP连接有两个端点,这两个端点是什么呢?不是主机,也不是主机IP,不是应用进程,也不是运输层的协议端口。TCP链接的端点叫做套接字(socket)=IP地址:端口号
TCP的三次握手和四次挥手
TCP的连接建立是一个三次握手过程,目的是为了通信双方确认开始序号,以便后续
通信的有序进行。主要步骤如下:
- 连接开始时,连接建立方(Client)发送SYN包,并包含了自己的初始序号a;——连接请求
- 连接接受方(Server)收到SYN包以后会回复一个SYN包,其中包含了对上一个a包
的回应信息ACK,回应的序号为下一个希望收到包的序号,即a+1,然后还包含
了自己的初始序号b;——请求确认 - 连接建立方(Client)收到回应的SYN包以后,回复一个ACK包做响应,其中包含了
下一个希望收到包的序号即b+1。——连接确认
TCP终止连接的四次握手过程如下: - 首先进行关闭的一方(即发送第一个FIN)将执行主动关闭,而另一方(收到这
个FIN)执行被动关闭。 - 当服务器收到这个FIN,它发回一个ACK,确认序号为收到的序号加1。和SYN一
样,一个FIN将占用一个序号。 - 同时TCP服务器还向应用程序(即丢弃服务器)传送一个文件结束符。接着这个
服务器程序就关闭它的连接,导致它的TCP端发送一个FIN。 - 客户必须发回一个确认,并将确认序号设置为收到序号加1。
2.网络设备概述
2.1、网卡
(1)计算机上网必备硬件设备,CPU靠网卡来连接外部网络
(2)串转并设备
(3)数据帧封包和拆包
(4)网络数据缓存和速率适配
2.2、集线器(HUB)
(1)信号中继放大,相当于中继器
(2)组成局域网络,用广播方式工作。
(3)注意集线器是不能用来连接外网的,只能用来组成局域网
2.3、交换机
(1)包含集线器功能,但更高级
(2)交换机中有地址表,数据包查表后直达目的通信口而不是广播
(3)找不到目的口时广播并学习
2.4、路由器
(1)路由器是局域网和外部网络通信的出入口
(2)路由器将整个internet划分成一个个的局域网,却又互相联通。
可在浏览器中输入路由器的网络地址,配置路由器
(3)路由器对内管理子网(局域网),可以在路由器中设置子网的网段,设置有线端口的IP地址,设置dhcp功能等,因此局域网的IP地址是路由器决定的。
(4)路由器对外实现联网,联网方式取决于外部网络(如ADSL拨号上网、宽带帐号、局域网等)。这时候路由器又相当于是更高层级网络的其中一个节点而已。
(5)所以路由器相当于有2个网卡,一个对内做网关、一个对外做节点。
(6)路由器的主要功能是为经过路由器的每个数据包寻找一条最佳路径(路由)并转发出去。其实就是局域网内电脑要发到外网的数据包,和外网回复给局域网内电脑的数据包。
(7)路由器技术是网络中最重要技术,决定了网络的稳定性和速度。
(8)路由器的两种接口:WAN连接外网、LAN连接内网
3.应用层概述
3.1、DNS(Domain Name Service 域名服务)
(1)网络世界的门牌号:IP地址
(2)IP地址的缺点:难记、不直观
(3)IP地址的替代品:域名,譬如www.zhulaoshi.org
(4)DNS服务器就是专门提供域名和IP地址之间的转换的服务的,因此域名要购买的
(5)我们访问一个网站的流程是:先使用IP地址(譬如谷歌的DNS服务器IP地址为8.8.8.8)访问DNS服务器(DNS服务器不能是域名,只能是直接的IP地址),查询我们要访问的域名的IP地址,然后再使用该IP地址访问我们真正要访问的网站。这个过程被浏览器封装屏蔽,其中使用的就是DNS协议。
(6)浏览器需要DNS服务,而QQ这样的客户端却不需要(因为QQ软件编程时已经知道了腾讯的服务器的IP地址,因此可以直接IP方式访问服务器)
3.2、DHCP(dynamic host configuration protocl,动态主机配置协议)
(1)每台计算机都需要一个IP地址,且局域网内各电脑IP地址不能重复,否则会地址冲突。
(2)计算机的IP地址可以静态设定,也可以动态分配
(3)动态分配是局域网内的DHCP服务器来协调的,很多设备都能提供DHCP功能,譬如路由器。
(4)动态分配的优势:方便接入和断开、有限的IP地址得到充分利用
3.3、NAT(network address translation,网络地址转换协议)
(1)IP地址分为公网IP(internet范围内唯一的IP地址)和私网IP(内网IP),局域网内的电脑使用的都是私网IP(常用的就是192.168.1.xx)
(2)网络通信的数据包中包含有目的地址的IP地址
(3)当局域网中的主机要发送数据包给外网时,路由器要负责将数据包头中的局域网主机的内网IP替换为当前局域网的对外外网IP。这个过程就叫NAT。
(4)NAT的作用是缓解IPv4的IP地址不够用问题,但只是类似于打补丁的形式,最终的解决方案还是要靠IPv6。
(5)NAT穿透简介
比如迅雷下载支持P2P下载,比方说一台电脑想下载东西,而在他周围有一台或者多台电脑有这个资源,但是因为电脑只有内网IP,而且不知道对方的路由器IP,所以这两台电脑不能直接相连,我们能连接网站是因为服务器有域名,我们能通过域名找到服务器的外网IP,迅雷就提供了一个功能:获得下载了迅雷客户端的电脑的IP地址,然后将IP地址告知给其他电脑,让两个电脑可以直接相连,所以一个电脑想下载东西,就会有有相应资源的并且离得近的多台电脑相连,下载速度就会很快。
4.IP地址分类(IPv4)
(1)IP地址实际是一个32位二进制构成,在网络通信数据包中就是32位二进制,而在人机交互中使用点分十进制方式显示。
(2)IP地址中32位实际包含2部分,分别为:网络地址和主机地址。子网掩码,用来说明网络地址和主机地址各自占多少位。
(3)由网络地址和主机地址分别占多少位的不同,将IP地址分为5类,最常用的有3类
(4)三类IP地址:A类、B类、C类
(5)有些特殊的IP地址作特殊的作用,比如127.0.0.0用来做回环测试loopback
4.2、如何判断2个IP地址是否在同一子网内
(1)网络标识 = IP地址 & 子网掩码
(2)2个IP地址的网络标识一样,那么就处于同一网络。
源IP地址:发出数据包的网络的IP地址
目标IP地址:要接收数据包的计算机的IP地址
(3) 二进制方式 0xffffffff 0xC0A80166/0x6601A8C0(大小端)
点分十进制方式 255.255.255.255 192.168.1.102
(4)IP地址 = 网络地址 + 主机地址
网络地址用来表示子网
主机地址是用来表示子网中的具体某一台主机的。
192.168.1.4和192.168.12.5,如果子网掩码是255.255.255.0那么不在同一网段,如果子网掩码是255.255.0.0那么就在同一个网段
5.linux网络编程实践
5.1.socket编程接口介绍
1.1、建立连接
(1)socket。socket函数类似于open,用来打开一个网络连接,如果成功则返回一个网络文件描述符(int类型),之后我们操作这个网络连接都通过这个网络文件描述符。int socket(int domain, int type, int protocol);
domain我们一般选择IPV4
type:SOCK_STREAM——TCP协议 SOCK_DGRAM——UDP协议
protocol一般是0
(2)bind:int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
sockfd:上面socket函数得到的返回值,网络文件描述符
sockaddr:IP地址结构体
socklen_t:sockaddr结构体长度
(3)listen:int listen(int sockfd, int backlog);
(4)accept:int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
(5)connect:int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
返回值表示是否建立连接成功
1.2、发送和接收
(1)write:ssize_t write(int fd, const void *buf, size_t count);
send:ssize_t send(int sockfd, const void *buf, size_t len, int flags);
正常通信下flag都是0,只有在特殊协议下flag标准才不同。
(2) read:ssize_t read(int fd, void *buf, size_t count);
recv:ssize_t recv(int sockfd, void *buf, size_t len, int flags);
1.3、辅助性函数:主要用来IP地址转换
(1)inet_aton、inet_addr、inet_ntoa(不支持IPV6)
(2)inet_ntop、inet_pton(点分十进制的形式转换成32位形式)
1.4、表示IP地址相关数据结构
(1)都定义在 netinet/in.h
(2)struct sockaddr,这个结构体是网络编程接口中用来表示一个IP地址的,注意这个IP地址是不区分IPv4和IPv6的(或者说是兼容IPv4和IPv6的)
(3)typedef uint32_t in_addr_t; 网络内部用来表示IP地址的类型
(4)
struct in_addr
{
in_addr_t s_addr;
};
(5)
struct sockaddr_in
{
__SOCKADDR_COMMON (sin_);
in_port_t sin_port;
struct in_addr sin_addr;
unsigned char sin_zero[sizeof (struct sockaddr) -
__SOCKADDR_COMMON_SIZE -
sizeof (in_port_t) -
sizeof (struct in_addr)];
};
__SOCKADDR_COMMON宏定义最终会定义一个sin_family元素,表示是IPV4还是IPV6,AF_INET,AF_INET6
(6)struct sockaddr 这个结构体是linux的网络编程接口中用来表示IP地址的标准结构体,bind、connect等函数中都需要这个结构体,这个结构体是兼容IPV4和IPV6的。在实际编程中这个结构体会被一个struct sockaddr_in或者一个struct sockaddr_in6所填充。
比方说bind需要一个结构体叫struct sockaddr,那我们就用struct sockaddr_in加上强制类型转换无缝填充到bind函数中。
1.5、发送接收除了用recv/send也可以用write/read
5.2.IP地址格式转换函数实践
2.1、inet_addr、inet_ntoa、inet_aton
2.2、inet_pton、inet_ntop
网络字节序,网络中统一使用大端模式。这些函数都考虑了网络字节序的问题。
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define IPADDR "192.168.1.102"
int main(void)
{
#if 0
struct in_addr addr = {0};
char buf[50] = {0};
addr.s_addr = 0x6703a8c0;
inet_ntop(AF_INET, &addr, buf, sizeof(buf));
printf("ip addr = %s.\n", buf);
#endif
#if 0
int ret = 0;
struct in_addr addr = {0};
ret = inet_pton(AF_INET, IPADDR, &addr);
if (ret != 1)
{
printf("inet_pton error\n");
return -1;
}
printf("addr = 0x%x.\n", addr.s_addr);
#endif
#if 1
in_addr_t addr = 0;
addr = inet_addr(IPADDR);
printf("addr = 0x%x.\n", addr);
#endif
return 0;
}
5.3.服务器和客户端程序
3.1、服务器端程序编写
(1)socket、(2)bind、(3)listen
(4)accept,返回值是一个fd,accept正确返回就表示我们已经和前来连接我的客户端之间建立了一个TCP连接了,以后我们就要通过这个连接来和客户端进行读写操作,读写操作就需要一个fd,这个fd就由accept来返回了。
注意:socket返回的fd叫做监听fd,是用来监听客户端的,不能用来和任何客户端进行读写;accept返回的fd叫做连接fd,用来和连接那端的客户端程序进行读写。
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#define SERPORT 9003
#define SERADDR "192.168.129.128"
#define BACKLOG 100
int main(void)
{
int sockfd = -1, ret = -1, clifd = -1;
socklen_t len = 0;
struct sockaddr_in seraddr = {0};
struct sockaddr_in cliaddr = {0};
char ipbuf[30] = {0};
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == sockfd){
perror("socket");
return -1;
}
printf("socketfd = %d.\n", sockfd);
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(SERPORT);
seraddr.sin_addr.s_addr = inet_addr(SERADDR);
ret = bind(sockfd, (const struct sockaddr *)&seraddr, sizeof(seraddr));
if (ret < 0){
perror("bind");
return -1;
}
printf("bind success.\n");
ret = listen(sockfd, BACKLOG);
if (ret < 0){
perror("listen");
return -1;
}
clifd = accept(sockfd, (struct sockaddr *)&cliaddr, &len);
printf("连接已经建立,client fd = %d.\n", ret);
return 0;
}
3.2、客户端程序编写
(1)socket、(2)connect
定义端口号时需要遵循一些规则:端口号小于256的定义为常用端口,服务器一般都是通过常用端口号来识别的。任何TCP/IP实现所提供的服务都用1—1023之间的端口号,是由ICANN来管理的;端口号从1024—49151是被注册的端口,也成为“用户端口”,被IANA指定为特殊服务使用。 大多数TCP/IP实现给临时端口号分配1024—5000之间的端口号。
而且端口号需要注意大小端问题,解决大小端问题函数:
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
概念:端口号,实质就是一个数字编号,用来在我们一台主机中(主机的操作系统中)唯一的标识一个能上网的进程。端口号和IP地址一起会被打包到当前进程发出或者接收到的每一个数据包中。每一个数据包将来在网络上传递的时候,内部都包含了发送方和接收方的信息(就是IP地址和端口号),所以IP地址和端口号这两个往往是打包在一起不分家的。
服务器需要指定端口号,因为客户端连接服务器必须知道服务器的端口号,客户端的端口号不用定义,因为会自动分配,为什么可以自动分配,因为服务器不需要知道客户端端口号。
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#define SERADDR "192.168.129.128"
#define SERPORT 9003
int main(void)
{
int sockfd = -1, ret = -1;
struct sockaddr_in seraddr = {0};
struct sockaddr_in cliaddr = {0};
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == sockfd){
perror("socket");
return -1;
}
printf("socketfd = %d.\n", sockfd);
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(SERPORT);
seraddr.sin_addr.s_addr = inet_addr(SERADDR);
ret = connect(sockfd, (const struct sockaddr *)&seraddr, sizeof(seraddr));
if (ret < 0){
perror("listen");
return -1;
}
printf("connect result, ret = %d.\n", ret);
return 0;
}
5.4.发送&接收程序
4.1、客户端发送&服务器接收
4.2、服务器发送&客户端接收
4.3、探讨:如何让服务器和客户端好好沟通
(1)客户端和服务器原则上都可以任意的发和收,但是实际上双方必须配合:client发的时候server就收,而server发的时候client就收
(2)必须了解到的一点:client和server之间的通信是异步的,这就是问题的根源
(3)解决方案:依靠应用层协议来解决。说白了就是我们server和client事先做好一系列的通信约定。
4.4、自定义应用层协议第一步:规定发送和接收方法
(1)规定连接建立后由客户端主动向服务器发出1个请求数据包,然后服务器收到数据包后回复客户端一个回应数据包,这就是一个通信回合
(2)整个连接的通信就是由N多个回合组成的。
4.5、自定义应用层协议第二步:定义数据包格式
4.6、例子客户端向服务器注册学生信息:
client.c:
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#define SERADDR "192.168.1.141"
#define SERPORT 9003
char sendbuf[100];
char recvbuf[100];
#define CMD_REGISTER 1001
#define CMD_CHECK 1002
#define CMD_GETINFO 1003
#define STAT_OK 30
#define STAT_ERR 31
typedef struct commu
{
char name[20];
int age;
int cmd;
int stat;
}info;
int main(void)
{
int sockfd = -1, ret = -1;
struct sockaddr_in seraddr = {0};
struct sockaddr_in cliaddr = {0};
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == sockfd){
perror("socket");
return -1;
}
printf("socketfd = %d.\n", sockfd);
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(SERPORT);
seraddr.sin_addr.s_addr = inet_addr(SERADDR);
ret = connect(sockfd, (const struct sockaddr *)&seraddr, sizeof(seraddr));
if (ret < 0){
perror("listen");
return -1;
}
printf("成功建立连接\n");
while (1)
{
info st1;
printf("请输入学生姓名\n");
scanf("%s", st1.name);
printf("请输入学生年龄");
scanf("%d", &st1.age);
st1.cmd = CMD_REGISTER;
ret = send(sockfd, &st1, sizeof(info), 0);
printf("发送了1个学生信息\n");
memset(&st1, 0, sizeof(st1));
ret = recv(sockfd, &st1, sizeof(st1), 0);
if (st1.stat == STAT_OK)
printf("注册学生信息成功\n");
else
printf("注册学生信息失败\n");
}
return 0;
}
server.c:
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#define SERPORT 9003
#define SERADDR "192.168.1.141"
#define BACKLOG 100
char recvbuf[100];
#define CMD_REGISTER 1001
#define CMD_CHECK 1002
#define CMD_GETINFO 1003
#define STAT_OK 30
#define STAT_ERR 31
typedef struct commu
{
char name[20];
int age;
int cmd;
int stat;
}info;
int main(void)
{
int sockfd = -1, ret = -1, clifd = -1;
socklen_t len = 0;
struct sockaddr_in seraddr = {0};
struct sockaddr_in cliaddr = {0};
char ipbuf[30] = {0};
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == sockfd){
perror("socket");
return -1;
}
printf("socketfd = %d.\n", sockfd);
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(SERPORT);
seraddr.sin_addr.s_addr = inet_addr(SERADDR);
ret = bind(sockfd, (const struct sockaddr *)&seraddr, sizeof(seraddr));
if (ret < 0)
{
perror("bind");
return -1;
}
printf("bind success.\n");
ret = listen(sockfd, BACKLOG);
if (ret < 0)
{
perror("listen");
return -1;
}
clifd = accept(sockfd, (struct sockaddr *)&cliaddr, &len);
printf("连接已经建立,client fd = %d.\n", clifd);
while (1)
{
info st;
ret = recv(clifd, &st, sizeof(info), 0);
if (st.cmd == CMD_REGISTER)
{
printf("用户要注册学生信息\n");
printf("学生姓名:%s,学生年龄:%d\n", st.name, st.age);
st.stat = STAT_OK;
ret = send(clifd, &st, sizeof(info), 0);
}
if (st.cmd == CMD_CHECK)
{
}
if (st.cmd == CMD_GETINFO)
{
}
}
return 0;
}
上面的server只能接受一个client,但是现实是需要一个server接受多个client的请求,这里我们扩展一种最简单的实现接受多请求的方法,使用我们之前学过的poll函数:
5.5poll多路监听
server.c:
#include<stdio.h>
#include<errno.h>
#include<string.h>
#include<stdlib.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<poll.h>
#include<unistd.h>
#define IPADDRESS "127.0.0.1"
#define PORT 9090
#define LISTENNUM 5
#define MAXOPEN 1000
int socketBind(char *ip,int port)
{
int listenfd ;
struct sockaddr_in servAddr ;
listenfd = socket(AF_INET,SOCK_STREAM,0) ;
if (listenfd < 0)
{
printf("listenfd create is error:%d\n",errno) ;
return -1 ;
}
bzero(&servAddr,sizeof(struct sockaddr_in)) ;
servAddr.sin_family= AF_INET ;
servAddr.sin_addr.s_addr = inet_addr(ip) ;
servAddr.sin_port = htons(port) ;
int ret = bind(listenfd,(struct sockaddr*)&servAddr,sizeof(servAddr)) ;
if (ret < 0)
{
printf("bind is error:%d\n",errno) ;
return -1 ;
}
return listenfd ;
}
int handleConnection(struct pollfd *connfds,int num)
{
int i , n ;
char buf[1024] ;
memset(buf,0,1024);
printf("deal the conn num:%d\n",num) ;
for(i = 1 ;i<=num;i++)
{
if(connfds[i].fd < 0)
continue ;
if(connfds[i].revents & POLLIN)
{
AGAIN:
n = read(connfds[i].fd,buf,1024) ;
if(n <0)
{
if (errno == EINTR)
{
goto AGAIN;
}
else
{
close(connfds[i].fd) ;
printf("close a conn index:%d,err:%d\n",i,errno) ;
connfds[i].fd = -1 ;
return -1;
}
}
else if(n==0)
{
printf("the socket is closed by the peer\n");
close(connfds[i].fd) ;
connfds[i].fd = -1 ;
}
else
{
buf[n] = '\0' ;
printf("recv data is :%s\n",buf) ;
write(connfds[i].fd,buf,n) ;
}
}
}
return 0 ;
}
int doPoll(int listenfd)
{
int nReady = 0 ;
int maxI = 0 ;
int connfd =-1 ;
int i = 0;
struct sockaddr_in cliAddr ;
socklen_t cliAddrLen ;
struct pollfd clientfds[MAXOPEN] ;
cliAddrLen = sizeof(struct sockaddr) ;
memset(&cliAddr,0,sizeof(struct sockaddr_in)) ;
clientfds[0].fd = listenfd ;
clientfds[0].events = POLLIN ;
clientfds[0].revents = 0 ;
for (i = 1 ;i<MAXOPEN ;i++ )
{
clientfds[i].fd = -1 ;
}
for(;;)
{
printf("poll start...\n") ;
nReady = poll(clientfds,maxI+1,-1) ;
if(nReady ==-1)
{
if(errno == EINTR)
continue ;
else
exit(-1) ;
}
if(clientfds[0].revents & POLLIN)
{
printf("accept the connectin\n") ;
connfd = accept(listenfd,(struct sockaddr*)&cliAddr,&cliAddrLen) ;
printf("connfd:%d\n",connfd) ;
if (connfd < 0)
{
printf("error:%d\n",errno) ;
if (errno == EINTR)
{
continue ;
}
else
{
printf("some error:%d\n",errno) ;
exit(-1) ;
}
}
printf("accept a new client:%s,%d\n",inet_ntoa(cliAddr.sin_addr),ntohs(cliAddr.sin_port)) ;
for(i= 1 ;i<MAXOPEN;i++)
{
if(clientfds[i].fd < 0)
{
clientfds[i].fd = connfd ;
break ;
}
}
if (i == MAXOPEN)
{
printf("too many clients.\n") ;
exit(-1) ;
}
clientfds[i].events = POLLIN ;
clientfds[i].revents = 0 ;
maxI = (i > maxI) ? i:maxI;
if(--nReady <= 0 )
continue ;
}
else
{
printf("deal a conn\n") ;
handleConnection(clientfds,maxI) ;
}
}
return 0 ;
}
int main()
{
int listenfd ;
printf("init is ok\n") ;
listenfd = socketBind(IPADDRESS,PORT) ;
if (listenfd == -1)
{
return -1 ;
}
printf("create socket and bind is ok\n") ;
int ret = listen(listenfd,LISTENNUM) ;
if (ret < 0)
{
printf("listen is error\n") ;
exit(-1) ;
}
printf("deal the poll\n") ;
doPoll(listenfd) ;
printf("error\n") ;
close(listenfd) ;
return 0 ;
}
client.c:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<netinet/in.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<arpa/inet.h>
#define IP "127.0.0.1"
#define PORT 9090
#define MAXLINE 1024
int main()
{
struct sockaddr_in servAddr ;
socklen_t servAddrLen ;
int servfd = -1 ;
char buf[MAXLINE] = "Mr.luo" ;
AGAIN:
bzero(&servAddr,sizeof(struct sockaddr_in)) ;
servAddrLen = sizeof(struct sockaddr) ;
servAddr.sin_family = AF_INET ;
inet_pton(AF_INET,IP,&servAddr.sin_addr);
servAddr.sin_port = htons(PORT) ;
servfd = socket(AF_INET,SOCK_STREAM,0) ;
int ret = connect(servfd,(struct sockaddr*)&servAddr,servAddrLen) ;
if (ret < 0)
{
if (errno == EINTR)
{
close(servfd) ;
goto AGAIN;
}
else
{
printf("error connect to serv:%d\n",errno) ;
close(servfd) ;
exit(-1) ;
}
}
sleep(1);
while(1)
{
write(servfd,buf,strlen(buf)+1) ;
memset(buf,0,MAXLINE);
sleep(1);
read(servfd,buf,MAXLINE) ;
printf("read data is :%s\n",buf) ;
}
close(servfd);
return 0 ;
}
(代码来自https://blog.csdn.net/luojian5900339/article/details/54581852)
(thrift的simpleserver就是用的poll机制,thrift还提供了其他机制,具体看thrift server连载文章)
5.7、常用应用层协议:http、ftp······
5.8、UDP简介:和tcp不同,udp不需要建立连接。
其他网络编程函数:
http://blog.chinaunix.net/uid-30226910-id-5766449.html
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)