linux网络基础

2023-05-16

文章目录

  • 前言
  • 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的连接建立是一个三次握手过程,目的是为了通信双方确认开始序号,以便后续
通信的有序进行。主要步骤如下:

  1. 连接开始时,连接建立方(Client)发送SYN包,并包含了自己的初始序号a;——连接请求
  2. 连接接受方(Server)收到SYN包以后会回复一个SYN包,其中包含了对上一个a包
    的回应信息ACK,回应的序号为下一个希望收到包的序号,即a+1,然后还包含
    了自己的初始序号b;——请求确认
  3. 连接建立方(Client)收到回应的SYN包以后,回复一个ACK包做响应,其中包含了
    下一个希望收到包的序号即b+1。——连接确认
    TCP终止连接的四次握手过程如下:
  4. 首先进行关闭的一方(即发送第一个FIN)将执行主动关闭,而另一方(收到这
    个FIN)执行被动关闭。
  5. 当服务器收到这个FIN,它发回一个ACK,确认序号为收到的序号加1。和SYN一
    样,一个FIN将占用一个序号。
  6. 同时TCP服务器还向应用程序(即丢弃服务器)传送一个文件结束符。接着这个
    服务器程序就关闭它的连接,导致它的TCP端发送一个FIN。
  7. 客户必须发回一个确认,并将确认序号设置为收到序号加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		//封装IPV4地址的结构体,最常用的是前三个元素   
  {
    __SOCKADDR_COMMON (sin_);	//__SOCKADDR_COMMON宏定义最终会定义一个sin_family元素,表示是IPV4还是IPV6,AF_INET,AF_INET6
    in_port_t sin_port;                 /* Port number.  */
    struct in_addr sin_addr;            /* Internet address.  */
    /* Pad to size of `struct sockaddr'.  */
    unsigned char sin_zero[sizeof (struct sockaddr) -
                           __SOCKADDR_COMMON_SIZE -
                           sizeof (in_port_t) -
                           sizeof (struct in_addr)];
  };		//如果要用IPV6那么就需要IPV6的结构体

__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"
// 0x66		01	a8		c0
// 102		1	168		192
// 网络字节序,其实就是大端模式
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);		// 0x6601a8c0
#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>          /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>

#define SERPORT		9003
#define SERADDR		"192.168.129.128"		// ifconfig看到的
#define BACKLOG		100

int main(void)
{
	// 第1步:先socket打开文件描述符
	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);
	
	// 第2步:bind绑定sockefd和当前电脑的ip地址&端口号
	seraddr.sin_family = AF_INET;		// 设置地址族为IPv4
	seraddr.sin_port = htons(SERPORT);	// 设置地址的端口号信息
	seraddr.sin_addr.s_addr = inet_addr(SERADDR);	// 设置IP地址
	ret = bind(sockfd, (const struct sockaddr *)&seraddr, sizeof(seraddr));
	if (ret < 0){
		perror("bind");
		return -1;
	}
	printf("bind success.\n");
	
	// 第三步:listen监听端口
	ret = listen(sockfd, BACKLOG);		// 阻塞等待客户端来连接服务器
	if (ret < 0){
		perror("listen");
		return -1;
	}
	
	// 第四步:accept阻塞等待客户端接入
	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>          /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>

#define SERADDR		"192.168.129.128"		// 服务器开放给我们的IP地址和端口号
#define SERPORT		9003

int main(void)
{
	// 第1步:先socket打开文件描述符
	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);
	
	// 第2步:connect链接服务器
	seraddr.sin_family = AF_INET;		// 设置地址族为IPv4
	seraddr.sin_port = htons(SERPORT);	// 设置地址的端口号信息
	seraddr.sin_addr.s_addr = inet_addr(SERADDR);	// 设置IP地址
	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>          /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#define SERADDR		"192.168.1.141"		// 服务器开放给我们的IP地址和端口号
#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		// 回复ok
#define STAT_ERR		31		// 回复出错了

typedef struct commu
{
	char name[20];		// 学生姓名
	int age;			// 学生年龄
	int cmd;			// 命令码
	int stat;			// 状态信息,用来回复
}info;

int main(void)
{
	// 第1步:先socket打开文件描述符
	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);
	
	// 第2步:connect链接服务器
	seraddr.sin_family = AF_INET;		// 设置地址族为IPv4
	seraddr.sin_port = htons(SERPORT);	// 设置地址的端口号信息
	seraddr.sin_addr.s_addr = inet_addr(SERADDR);	// 设置IP地址
	ret = connect(sockfd, (const struct sockaddr *)&seraddr, sizeof(seraddr));
	if (ret < 0){
		perror("listen");
		return -1;
	}
	printf("成功建立连接\n");

/*
	while (1)
	{
		// 回合中第1步:客户端给服务器发送信息
		printf("请输入要发送的内容\n");
		scanf("%s", sendbuf);
		//printf("刚才输入的是:%s\n", sendbuf);
		ret = send(sockfd, sendbuf, strlen(sendbuf), 0);
		printf("发送了%d个字符\n", ret);
		
		// 回合中第2步:客户端接收服务器的回复
		memset(recvbuf, 0, sizeof(recvbuf));
		ret = recv(sockfd, recvbuf, sizeof(recvbuf), 0);
		//printf("成功接收了%d个字节\n", ret);
		printf("client发送过来的内容是:%s\n", recvbuf);

		// 回合中第3步:客户端解析服务器的回复,再做下一步定夺
		
	}
*/
	while (1)
	{
		// 回合中第1步:客户端给服务器发送信息
		info st1;
		printf("请输入学生姓名\n");
		scanf("%s", st1.name);
		printf("请输入学生年龄");
		scanf("%d", &st1.age);
		st1.cmd = CMD_REGISTER;
		//printf("刚才输入的是:%s\n", sendbuf);
		ret = send(sockfd, &st1, sizeof(info), 0);
		printf("发送了1个学生信息\n");
		
		// 回合中第2步:客户端接收服务器的回复
		memset(&st1, 0, sizeof(st1));
		ret = recv(sockfd, &st1, sizeof(st1), 0);
		
		// 回合中第3步:客户端解析服务器的回复,再做下一步定夺
		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>          /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#define SERPORT		9003
#define SERADDR		"192.168.1.141"		// ifconfig看到的
#define BACKLOG		100
char recvbuf[100];
#define CMD_REGISTER	1001	// 注册学生信息
#define CMD_CHECK		1002	// 检验学生信息
#define CMD_GETINFO		1003	// 获取学生信息

#define STAT_OK			30		// 回复ok
#define STAT_ERR		31		// 回复出错了

typedef struct commu
{
	char name[20];		// 学生姓名
	int age;			// 学生年龄
	int cmd;			// 命令码
	int stat;			// 状态信息,用来回复
}info;

int main(void)
{
	// 第1步:先socket打开文件描述符
	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);
	
	// 第2步:bind绑定sockefd和当前电脑的ip地址&端口号
	seraddr.sin_family = AF_INET;		// 设置地址族为IPv4
	seraddr.sin_port = htons(SERPORT);	// 设置地址的端口号信息
	seraddr.sin_addr.s_addr = inet_addr(SERADDR);	// 设置IP地址
	ret = bind(sockfd, (const struct sockaddr *)&seraddr, sizeof(seraddr));
	if (ret < 0)
	{
		perror("bind");
		return -1;
	}
	printf("bind success.\n");
	
	// 第三步:listen监听端口
	ret = listen(sockfd, BACKLOG);		// 阻塞等待客户端来连接服务器
	if (ret < 0)
	{
		perror("listen");
		return -1;
	}
	
	// 第四步:accept阻塞等待客户端接入
	clifd = accept(sockfd, (struct sockaddr *)&cliaddr, &len);
	printf("连接已经建立,client fd = %d.\n", clifd);
	
	// 客户端反复给服务器发
	while (1)
	{
		info st;
		// 回合中第1步:服务器收
		ret = recv(clifd, &st, sizeof(info), 0);

		// 回合中第2步:服务器解析客户端数据包,然后干活,
		if (st.cmd == CMD_REGISTER)
		{
			printf("用户要注册学生信息\n");
			printf("学生姓名:%s,学生年龄:%d\n", st.name, st.age);
			// 在这里服务器要进行真正的注册动作,一般是插入数据库一条信息
			
			// 回合中第3步:回复客户端
			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

/*
    @funcation name : socketBind 
    @description: create socket and bind the address
    @return
        succ : listenfd 
        fail : -1 
*/
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) ;
    //inet_pton(AF_INET,ip,&servAddr.sin_addr) ;
    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 ;

}
/*
    @funcation name : handleConnection 
    @description: handle the client socket 
    @return
        succ : 0 
        fail : -1 
*/
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 ;
}

/*
    @funcation name : doPoll 
    @description: deal the poll of sockets;
           this is main loop;
    @return
        succ : 0 
        fail : exit(-1)  ;
*/
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 ;
}

/*
    @funcation name : main 
    @description: 
    @return
        succ :  0
        fail : exit-1 )
*/
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_addr.s_addr = inet_addr(IP) ;
    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(使用前将#替换为@)

linux网络基础 的相关文章

  • 飞浆七天深度学习

    文章目录 前言图像处理原理与深度学习入门百度的飞浆实战可视化数据准备可视化效果图片 深度学习原理与使用方法实战手势识别 卷积神经网络原理与使用实战车牌识别分割字符的代码定义MyLeNet网络 经典卷积网络解读数据增强计算VGG的参数小姐姐手
  • Ubuntu18.04安装教程——超详细的图文教程

    电脑配置 xff1a 名称 xff1a Lenovo 拯救者Y7000P 处理器 xff1a i7 10750H 内存 xff1a 32G 固态 xff1a 1TB 显卡 xff1a RTX2060 6G 一 准备工作 本文以 Ubuntu
  • VINS-Course代码解析——run_euroc前端数据处理

    vins mono总框架如下 xff1a 主要分为三大块 xff1a 我们先从主函数 main 入手 xff1a 主函数中有三个线程 xff0c 读取完数据集和配置文件的路径后就会进入这三个线程 xff0c 如下图 xff1a thd Ba
  • C++求解N个数的最大公约数、最小公倍数

    一 2个数的最大公约数 span class token comment 辗转相除法 span span class token keyword int span span class token function gcd span spa
  • 子序列个数——动态规划

    题目 xff1a 统计一个字符串中全部不同的子序列的个数 思路 xff1a 动态规划求解 令 f i 61 前 i 个元素中包含的全部子序列的个数 那么状态转移方程分为下面两种情况 xff1a 当第 i 个元素在前面 i 1 个字符中没有出
  • 字符串中特定子序列出现的次数(动态规划)

    题目 xff1a 给定一个字符串 xff0c 求子序列 cwbc 出现的次数 思路 xff1a 动态规划 令 dp i j 表示前 i 个字符中匹配了字符串 cwbc 中前 j 位 xff08 j 61 1 2 3 4 xff09 的个数
  • 认识node

    一 认识node node是一个基于Chrome V8引擎的JavaScript代码运行环境 浏览器 xff08 软件 xff09 能够运行JavaScript代码 xff0c 浏览器就是JavaScript代码的运行环境 xff08 js
  • python爬虫-使用request,lxml库爬取游戏排名

    爬取目标URL xff1a http wy hao123 com top 开发环境 xff1a PyCharm 2019 2 3Python3 6火狐浏览器 使用的三方库 xff1a requestslxml 执行结果 开始 抓取网页 打开
  • 知识追踪模型——教育大数据挖掘(持续更新......)

    知识追踪 xff08 2015 NIPS xff09 Deep Knowledge Tracing xff08 2017 WWW xff09 Dynamic Key Value Memory Networks for Knowledge T
  • Wireshark从安装到使用详细指南

    前言 wireshark是一款非常优秀的网络封包分析软件 xff0c 具有极为强大的功能 可以截取各种类型的网络封包 xff0c 并且显示网络封包的详细信息 值得一提的是 xff0c 为了安全性考虑 xff0c wireshark无法实现改
  • echarts随dom大小自适应变化,并做防抖处理

    目录 监听窗口resize事件监听dom的resize事件完整代码示例 监听窗口resize事件 监听浏览器窗口resize事件很简单 xff0c 如下一行代码即可搞定 xff1a window span class token punct
  • 极简版qt打包成exe

    文章目录 极简版qt打包成exe附加工具过程 极简版qt打包成exe 附加工具 Engima Virtual Box xff0c 将qt的多个文件打包成一个exe xff0c 下载地址 过程 新建xxx文件夹将初步编译过的xxx exe x
  • Windows10清理C盘的恶意软件

    Windows10清理C盘的恶意软件 写在前面情况1 xff1a 在桌面留有 快捷链接 的情况2 xff1a 在 控制面板 的 卸载软件 中能找到名字情况3 xff1a 在 控制面板 的 卸载软件 中找不到名字1 ludashi birdw
  • 前端的学习之路:初级CSS---关系选择器

    关系选择器 span class token doctype lt DOCTYPE html gt span span class token tag span class token tag span class token punctu
  • 子类继承父类的加载顺序——记录篇

    两种情况的加载顺序 xff1a 初始化阶段的初始化步骤 xff1a 静态代码块 先父类后子类 gt 非静态代码块 gt 父类 先父类后子类 一 单独类的加载顺序 静态变量 静态代码块 xff1a 从上到下的顺序加载 类的非静态变量 xff0
  • “完美”解决Mybatis+PageHelper出现SQL语句末尾自动加limit出现SQL语法错误

    问题比较简单 xff0c 解决方法也比较简单 xff0c 这里简单描述一下 具体描述请看这里 xff1a 问题详细描述 产生原因 Mybatis 43 Pagehelper在执行分页逻辑时时 xff0c 会默认在你的写的SQL语句后面添加l
  • Edge扩展插件安装位置

    Edge插件默认位置在 xff1a C Users Administrator AppData Local Microsoft Edge User Data Default Extensions 若查找不到 xff0c 则您的用户名更改了
  • mac上zsh: command not found:${命令} 失败问题

    重新安装了zsh后 xff0c 输入很多以前能够用的alias命令缩写都不能用了 xff0c 原因 xff1a 原来的命令缩写写在 bash profile文件中 xff0c 这个使用的是bash xff1b 但是zsh是另外一个shell
  • linux安装并卸载图形化界面+克隆虚拟机

    首先我们要了解图形界面有多少 xff0c 以及他们的优缺点都是什么 这篇博客可以让你完美的解决这些问题四种桌面环境的比较 在安装界面以及卸载时 xff0c 我们经常会用到 yum 这一个单词 那么yum到底是什么意思呢 xff1f yum的
  • 【洛谷】P1605 迷宫(dfs) 题解

    原题链接 xff1a https www luogu org problem P1605 题目描述 给定一个N M方格的迷宫 xff0c 迷宫里有T处障碍 xff0c 障碍处不可通过 给定起点坐标和终点坐标 xff0c 问 每个方格最多经过

随机推荐