Linux系统编程——网络编程

目录

一、对于Socket、TCP/UDP、端口号的认知:

1.1 什么是Socket:

1.2 TCP/UDP对比:

1.3 端口号的作用:

二、字节序

2.1 字节序相关概念:

2.2 为什么会有字节序:

2.3 主机字节序转换成网络字节序函数原型和头文件:

2.4 网络字节序转换成主机字节序函数原型和头文件:

三、socket服务器和客户端开发步骤

3.1 TCP通信流程:

3.2 UDP通信流程:

四、socket 相关函数

4.1 创建套接字函数socket()原型和头文件:

4.2 绑定套接字函数bind()原型和头文件:

4.3 字符串格式的IP地址转换成网络格式函数inet_aton()原型和头文件:

4.4 网络格式的IP地址转换成字符串格式函数inet_ntoa()原型和头文件:

4.5 监听被绑定的端口函数listen()原型和头文件:

4.6 接收客户端连接请求函数accept()原型和头文件:

4.7 客户端发送连接请求函数connect()原型和头文件:

4.8 TCP发送信息函数send()原型和头文件:

4.9 TCP接收信息函数recv()原型和头文件:

五、实现客户端&服务器通信

5.1 实现客户端和服务器双方聊天:

5.2 实现多个客户端接入服务器通信:


一、对于Socket、TCP/UDP、端口号的认知:

1.1 什么是Socket:

  • 所谓套接字Socket),就是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象。一个套接字就是网络上进程通信的一端,提供了应用层进程利用网络协议交换数据的机制。从所处的地位来讲,套接字上联应用进程,下联网络协议栈,是应用程序通过网络协议进行通信的接口,是应用程序与网络协议栈进行交互的接口。

  • 套接字是通信的基石,是支持TCP/IP协议的路通信的基本操作单元。可以将套接字看作不同主机间的进程进行双间通信的端点,它构成了单个主机内及整个网络间的编程界面。套接字存在于通信域中,通信域是为了处理一般的线程通过套接字通信而引进的一种抽象概念。套接字通常和同一个域中的套接字交换数据(数据交换也可能穿越域的界限,但这时一定要执行某种解释程序),各种进程使用这个相同的域互相之间用Internet协议簇来进行通信。

  • Socket套接字)可以看成是两个网络应用程序进行通信时,各自通信连接中的端点,这是一个逻辑上的概念。它是网络环境中进程间通信的API,也是可以被命名和寻址的通信端点,使用中的每一个套接字都有其类型和一个与之相连进程。通信时其中一个网络应用程序将要传输的一段信息写入它所在主机的 Socket中,该 Socket通过与网络接口卡(NIC)相连的传输介质将这段信息送到另外一台主机的 Socket中,使对方能够接收到这段信息。 Socket是由IP地址和端口结合的,提供向应用层进程传送数据包的机制。

  • Socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,都可以用“打开open –> 读写write/read –> 关闭close”模式来操作。Socket可以看作该模式的一个实现,Socket即是一种特殊的文件,一些Socket函数就是对其进行的操作(读/写IO、打开、关闭)

  • socket其实就是一根通信电缆两端的电话终端,电话接通后就相当两个socket建立了连接,两个电话之间可以相互通话,两个socket之间就可以实时收发数据,socket仅仅是一个通信工具,通信工具,通信工具重要的事说三遍(OSI模型中的第四层传输层的API接口,这一层通常使用两种协议TCP或UDP来传输)并不是一种协议。TCP、UDP、HTTP才是我们通常理解的协议

  • 也就是说,Socket这个工具一般使用TCP和UDP两种协议来通信,否则光杆socket并没有毛用。其实我们所认识到的互联网中的各种通信:web请求、即时通讯、文件传输和共享等等底层都是通过Socket工具来实现的,所以说互联网一切皆socket搞懂了socket你就相当于打通了任督二脉

  • 在UNIX、Linux系统中,为了统一对各种硬件的操作,简化接口,不同的硬件设备也都被看成一个文件。对这些文件的操作,等同于对磁盘上普通文件的操作。 为了表示和区分已经打开的文件,UNIX/Linux会为每个文件分配一个ID,这个文件就是一个整数,被称为文件描述符 例如: 通常用 0 来表示标准输入文件(stdin),它对应的硬件设备就是键盘; 通常用 1 来表示标准输出文件(stdout),它对应的硬件设备就是显示器。 网络连接也是一个文件,它也有文件描述符 我们可以通过 socket() 函数来创建一个网络连接,或者说打开一个网络文件,socket() 的返回值就是文件描述符(注意在windows下的socket返回的叫文件句柄,并不是叫文件描述符)。有了文件描述符,我们就可以使用普通的文件操作函数来传输数据了,例如: 用 read() 读取从远程计算机传来的数据; 用 write() 向远程计算机写入数据。

1.2 TCP/UDP对比:

  1. TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据前,不需要建立连接。

  2. TCP提供可靠的服务,也就是说通过TCP连接传送的数据是无差错,不丢失,不重复且按序到达;UDP是尽最大努力交付,即保证可靠交付

  3. TCP是面向字节流,实际上是TCP把数据看成是一连串无结构的字节流;UDP是面向报文的,UDP没有拥塞控制,因此网络出现拥塞不会是源主机的发送速率降低(对实时应用很有用,如IP电话,实时视频会议…)。

  4. 每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信

  5. TCP的首部开销20字节;UDP的首部开销小,只有8个字节。

  6. TCP是逻辑通信信道是全双工的可靠信道UDP是不可靠信道

1.3 端口号的作用:

一台拥有 IP 地址的主机可以提供许多服务,比如 Web 服务、FTP 服务、 SMTP 服务等。 这些服务完全可以通过 1 个 IP 地址来实现。那么,主机是怎样区分不同的网络服务呢?显然不能只靠 IP 地址,因为 IP 地址与网络服务的关系是一对多的关系实际上是通过“ IP 地址 + 端口号”来区分不同的服务的。端口提供了一种访问通道,服务器一般都是通过知名端口号来识别的。例如,对于每个 TCP/IP 实现来说,FTP服务器的 TCP 端口号都是 21 ,每个 Telnet 服务器的 TCP端口号都是 23 ,每个 TFTP( 简单文件传送协议 ) 服务器的 UDP 端口号都是 69 。

端口号,用两个字节表示的整数,它的取值范围是065535。其中0~1023之间的端口号用于一些知名的网络服务和应用,普通的应用程序需要使用1024以上的端口号。如果端口号被另外一个服务或应用所占用,会导致当前程序启动失败。(如果IP地址是相当于一栋楼的楼号的话,那么端口号就相当于是这栋楼里面的房间的房号

利用 协议 + IP地址 + 端口号 三元组合,就可以标识网络中的进程了,那么进程间的通信就可以利用这个标识与其它进程进行交互。

二、字节序

2.1 字节序相关概念:

  • 字节序是指多字节数据在计算机内存中存储或者网络传输时各字节的存储顺序。在设计计算机系统的时候,有两种处理内存中数据的方法:即大端字节序(大端格式)小端字节序(小端格式)

  • 小段字节序(Little endian):将低序字节存储在起始地址

  • 大端字节序(Big endian) :将高序字节存储在起始地址

内存地址小段字节序(Little endian)大端字节序(Big endian)
40000x040x01
40010x030x02
40020x020x03
40030x010x04

2.2 为什么会有字节序:

  • 计算机电路先处理低位字节,效率比较高,因为计算都是从低位开始的。所以,计算机的内部处理都是小端字节序。但是,人类还是习惯读写大端字节序。所以,除了计算机的内部处理,其他的场合几乎都是大端字节序,比如网络传输和文件储存。

  • 计算机处理字节序的时候,不知道什么是高位字节,什么是低位字节。它只知道按顺序读取字节,先读第一个字节,再读第二个字节。如果是大端字节序,先读到的就是高位字节,后读到的就是低位字节。小端字节序正好相反。只有读取的时候,才必须区分字节序,其他情况都不用考虑

2.3 主机字节序转换成网络字节序函数原型和头文件:

#include <arpa/inet.h>					// 包含对网络地址的操作函数的头文件uint16_t htons(uint16_t hostshort);		//将16位主机字节序数据转换成网络字节序数据
uint32_t htonl(uint32_t hostlong);		//将32位主机字节序数据转换成网络字节序数据uint16_t				函数返回值,成功返回网络字节序的值    
uint16_t hostshort		需要转换的16位主机字节序数据,uint16_t:unsigned short intuint32_t				函数返回值,成功返回网络字节序的值 
uint32_t hostlong		需要转换的32位主机字节序数据,uint32_t:32位无符号整型

2.4 网络字节序转换成主机字节序函数原型和头文件:

#include <arpa/inet.h>					// 包含对网络地址的操作函数的头文件uint16_t ntohs(uint16_t netshort);		//将32位网络字节序数据转换成主机字节序数据
uint32_t ntohl(uint32_t netlong);		//将16位网络字节序数据转换成主机字节序数据uint16_t			函数返回值,返回主机字节序的值
uint32_t netlong	需要转换的16位网络字节序数据;uint16_t:unsigned short intuint32_t			函数返回值,返回主机字节序的值
uint32_t netlong	需要转换的32位网络字节序数据;uint32_t:unsigned int

三、socket服务器和客户端开发步骤

3.1 TCP通信流程:

  • TCP传输控制协议 (Transmission Control Protocol)。TCP协议是面向连接的通信协议,即传输数据之前,在发送端和接收端建立逻辑连接,然后再传输数据,它提供了两台计算机之间可靠无差错的数据传输

  • 服务器Server

  1. 创建套接字(socket

  2. 将socket与IP地址和端口绑定(bind

  3. 监听被绑定的端口(listen

  4. 接收连接请求(accept

  5. 从socket中读取客户端发送来的信息(read

  6. 向socket中写入信息(write

  7. 关闭socket(close

  • 客户端Client

  1. 创建套接字(socket

  2. 连接指定计算机的端口(connect

  3. 向socket中写入信息(write

  4. 从socket中读取服务端发送过来的消息(read

  5. 关闭socket(close

3.2 UDP通信流程:

用户数据报协议(User Datagram Protocol)。UDP协议是一个面向无连接的协议传输数据时,不需要建立连接,不管对方端服务是否启动,直接将数据、数据源和目的地都封装在数据包中,直接发送。每个数据包的大小限制在64k以内。它是不可靠协议,因为无连接,所以传输速度快,但是容易丢失数据。日常应用中,例如视频会议、QQ聊天等。

  • 服务器Server

  1. 使用函数socket(),生成套接字文件描述符;

  2. 通过struct sockaddr_in 结构设置服务器地址和监听端口;

  3. 使用bind() 函数绑定监听端口,将套接字文件描述符和地址类型变量(struct sockaddr_in )进行绑定;

  4. 接收客户端的数据,使用recvfrom() 函数接收客户端的网络数据;

  5. 向客户端发送数据,使用sendto() 函数向服务器主机发送数据;

  6. 关闭套接字,使用close() 函数释放资源;

  • 客户端Client

  1. 使用socket(),生成套接字文件描述符;

  2. 通过struct sockaddr_in 结构设置服务器地址和监听端口;

  3. 向服务器发送数据,sendto()

  4. 接收服务器的数据,recvfrom()

  5. 关闭套接字,close()

四、socket 相关函数

4.1 创建套接字函数socket()原型和头文件:

/*Linux下 man 2 socket查看手册
*/
#include <sys/types.h>
#include <sys/socket.h>int socket(int domain, int type, int protocol);int 			函数返回值,成功返回非负套接字描述符,失败返回-1int domain		指明所使用的协议族,通常为AF_INET,表示互联网协议族(TCP/IP协议族)1. AF_INET IPv4因特网域2. AF_INET6 IPv6因特网域3. AF_UNIX Unix域4. AF_ROUTE 路由套接字5. AF_KEY 密钥套接字6. AF_UNSPEC 未指定int type		参数设定socket的类型1. SOCK_STREAM:流式套接字提供可靠的,面向连接的通信流,它使用TCP协议,从而保证了数据传输的正确性和顺序性2. SOCK_DGRAM:数据报套接字定义了一种无连接的服,数据通过相互独立的报文进行传输,是无序的,并且不保证是可靠,无差错的。它使用					数据报协议UDP3. SOCK_RAW:允许程序使用底层协议,原始套接字允许对底层协议如IP或ICMP进行直接访问,功能强大但使用较为不便,主要用于一些协议				  的开发。int protocol	通常赋值为“0”,0选择type对应的默认协议1. IPPROTO_TCP TCP传输协议2. IPPROTO_UDP UDP传输协议3. IPPROTO_SCTP SCTP传输协议4. IPPROTO_TIPC TIPC传输协议/*函数说明:用于创建套接字,同时指定协议和类型*/

4.2 绑定套接字函数bind()原型和头文件:

/*Linux下 man 2 bind查看手册
*/
#include <sys/types.h>
#include <sys/socket.h>int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);int 					函数返回值,如果成功则返回0,如果失败则返回-1int sockfd				是一个socket描述符struct sockaddr *addr	是一个指向包含有本机IP地址及端口号等信息的sockaddr类型的指针,指向要绑定给sockfd的协议地址结构,这						 个地址结构根据地址创建socket时的地址协议族的不同而不同。  
struct sockaddr {sa_family_t sa_family;//协议族char        sa_data[14];//IP+端口号
}
说明:sockaddr在头文件#include <sys/socket.h>中定义,sockaddr的缺陷是:sa_data把目标地址和端口信息混在一起了struct sockaddr_in {//如何找到这个结构体,在下方有详解__kernel_sa_family_t  sin_family; 	//协议族__be16                sin_port; 	//端口号     struct in_addr        sin_addr;		//IP地址结构体   unsigned char         __pad[__SOCK_SIZE__ - sizeof(short int) -sizeof(unsigned short int) - sizeof(struct in_addr)];/*填充 没有实际意义,只是为跟sockaddr结构在内存在内存中对其,这样两者才能相互*/
};
/* Internet address. */
struct in_addr
{uint32_t       s_addr;     /* address in network byte order */
};
说明:sockaddr_in在头文件#include<netinet/in.h>或#include <arpa/inet.h>中定义,该结构体解决了sockaddr的缺陷,把port和addr 分开储存在两个变量中上述两者结构体长度一样,都是16个字节,即占用的内存大小是一致的,因此可以互相转化。二者是并列结构,指向sockaddr_in结构的指针也可以指向sockaddr。
一般先把sockaddr_in变量赋值后,强制类型转换后传入用sockaddr做参数的函数:sockaddr_in用于socket定义和赋值;sockaddr用于函数参数。socklen_t addrlen		地址的长度,通常用sizeof(struct sockaddr_in)表示;/*函数说明:用于绑定IP地址和端口号到 socket*/

4.3 字符串格式的IP地址转换成网络格式函数inet_aton()原型和头文件:

/*Linux下 man inet_aton查看手册
*/
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>int inet_aton(const char *cp, struct in_addr *inp);const char *cp			你的IP地址
struct in_addr *inp		存放你这个IP地址指针结构体(在上面bind()中有这个结构体),例如:&s_addr/*函数说明:把字符串形式的IP地址如"192.168.1.123"装换为网络能识别的格式*/

4.4 网络格式的IP地址转换成字符串格式函数inet_ntoa()原型和头文件:

/*Linux下 man inet_ntoa查看手册
*/
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>char *inet_ntoa(struct in_addr inaddr);struct in_addr inaddr	存放网络格式IP地址的结构体(在上面bind()中有这个结构体)/*函数说明:把网络格式的IP地址转换成字符串形式*/

4.5 监听被绑定的端口函数listen()原型和头文件:

/*Linux下 man 2 listen查看手册
*/
#include <sys/types.h>     
#include <sys/socket.h>int listen(int sockfd, int backlog);int 			函数返回值,如果成功则返回0,如果失败则返回-1int sockfd		socket系统调用返回的服务端socket描述符
int backlog		指定在请求队列中允许的最大的请求数,大多数系统默认为5

函数功能:

  • 设置能处理的最大连接数,listen并未开始接受连线,只是设置了socketlisten模式,listen函数只用于服务器端,服务器进程不知道要与谁进行连接,因此,它不会主动的要求与某个进程连接,只是一直监听是否有其他客户进程与之连接,然后响应该连接请求,并对它做出处理,一个服务进程可以同时处理多个客户进程的连接,主要就连个功能:将一个未连接的套接字转换为一个被动套接字(监听),规定内核为相应套接字排队的最大连接数

  • 内核为任何一个给定监听套接字维护两个队列:

  • 未完成连接队列,每个这样的SYN报文段对应其中一项:已由某个客户端发出并到达服务器,而服务器正在等待完成相应的TCP三次握手过程,这些套接字处于SYN_REVD状态

  • 已完成连接队列,每个已完成TCP三次握手过程的客户端对应其中一项,这些套接字处于ESTABLISHED状态;

4.6 接收客户端连接请求函数accept()原型和头文件:

/*Linux下 man 2 accept查看手册
*/
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);int 					函数返回值,这些系统调用返回被接受套接字的文件描述符(一个非负整数)。如果出现错误,则返回-1该函数的返回值是一个新的套接字的描述符,返回值是表示已连接的套接字描述符,而第一个参数是服务器监听套接字描述符,一个服务器通常仅仅创建一个监听套接字,它在该服务器的生命周期内一直存在。内核为每个由服务器进程接受的客户连接创建一个已连接套接字(表示TCP三次握手已完成),当服务器完成对某个给定客户的服务时,相应的已连接套接字就会被关闭。int sockfd				是socket系统调用返回的服务器端socket描述符
struct sockaddr *addr	用来返回已连接的对端(客户端)的协议地址socklen_t *addrlen		客户端地址长度,注意需要取地址/*
函数说明:accept函数由TCP服务器调用,用于从已完成连接队列对头返回下一个已完成连接,如果已完成连接队列为空,那么进程被投入睡眠。
*/

4.7 客户端发送连接请求函数connect()原型和头文件:

/*Linux下 man 2 connect查看手册
*/
#include <sys/types.h> 
#include <sys/socket.h>int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);int 			函数返回值,如果连接或者绑定成功则返回0,如果失败则返回-1int sockfd				客户端创建的socket描述符
struct sockaddr *addr	是服务器端的IP地址和端口号的地址结构指针
socklen_t addrlen		地址的长度,通常被设置为 sizeof(struct sockaddr)/*函数说明:该函数用于绑定之后的client端(客户端),与服务器建立连接*/

4.8 TCP发送信息函数send()原型和头文件:

/*Linux下 man 2 send查看手册
*/
#include <sys/types.h>
#include <sys/socket.h>ssize_t send(int sockfd, const void *buf, size_t len, int flags);ssize_t			函数返回值,如果成功则返回实际发送的字节数,如果失败则返回-1int sockfd		为已建立好连接的套接字描述符即accept函数的返回值
void *buf		要发送的内容
size_t len		发送内容的长度
int flags		设置为MSG_DONTWAITMSG 时 表示非阻塞,设置为0时 功能和write一样/*函数说明:函数只能对处于连接状态的套接字进行使用,参数sockfd为已建立好连接的套接字描述符*/

4.9 TCP接收信息函数recv()原型和头文件:

/*Linux下 man 2 recv查看手册
*/
#include <sys/types.h>
#include <sys/socket.h>ssize_t recv(int sockfd, void *buf, size_t len, int flags);ssize_t			函数返回值,如果成功则返回实际发送的字节数,失败则返回-1int sockfd		在哪个套接字接收
void *buf		存放要接收的数据的首地址
size_t len		要接收的数据的字节大小
int flags		设置为MSG_DONTWAITMSG 时 表示非阻塞,设置为0时 功能和read一样/*函数说明:接收套接字中的数据*/

五、实现客户端&服务器通信

5.1 实现客户端和服务器双方聊天:

/*server1.c*/
#include <stdio.h>          // 包含标准输入输出头文件
#include <sys/types.h>      // 包含系统数据类型头文件
#include <sys/socket.h>     // 包含系统套接字库的头文件
#include <stdlib.h>         // 包含标准库头文件
#include <arpa/inet.h>      // 包含网络地址转换头文件
#include <netinet/in.h>     // 包含IPv4地址头文件
#include <string.h>         // 包含字符串头文件
#include <unistd.h>         // 包含unistd.h头文件int main(int argc, char **argv)
{int s_fd;                                                                  // 套接字文件描述符int c_fd;                                                                  // 客户端套接字文件描述符int n_read;                                                                // 读入字节数int n_write;                                                               // 写出字节数char readBuf[128] = {0};                                                   // 读入缓冲区char writeBuf[128] = {0};                                                   // 写出缓冲区   struct sockaddr_in server_addr;                                             // 服务器地址结构体struct sockaddr_in client_addr;                                             // 客户端地址结构体memset(&server_addr, 0, sizeof(server_addr));                                // 服务器地址结构体清零memset(&client_addr, 0, sizeof(client_addr));                                // 客户端地址结构体清零if(argc != 3){                                                               // 参数检查    printf("参数错误!请按照格式输入:./server IP地址 端口号\n");exit(-1);}//int socket(int domain, int type, int protocol);s_fd = socket(AF_INET, SOCK_STREAM, 0);                                     // 创建TCP/IP套接字if(s_fd == -1){printf("创建套接字失败!\n");perror("socket");                                                       // 输出错误信息exit(-1);}server_addr.sin_family = AF_INET;                                           // 设置服务器地址族为IPv4server_addr.sin_port   = htons(atoi(argv[2]));                              // 设置服务器端口号//inet_aton("127.0.0.1", &server_addr.sin_addr); inet_aton(argv[1], &server_addr.sin_addr);                                  // 设置服务器IP地址//int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);int ret = bind(s_fd,(struct sockaddr *)&server_addr,sizeof(server_addr));   // 绑定服务器地址if(ret == -1){printf("绑定服务器地址失败\n");perror("bind");                                                         // 输出错误信息exit(-1);}//int listen(int sockfd, int backlog);ret = listen(s_fd, 10);                                                      // 监听套接字if(ret == -1){printf("监听套接字失败\n");perror("listen");                                                        // 输出错误信息exit(-1);}printf("服务器启动成功!\n");int client_addr_len = sizeof(client_addr);                                    // 客户端地址长度//int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);while(1){printf("等待客户端连接...\n");c_fd = accept(s_fd, (struct sockaddr *)&client_addr, &client_addr_len);       // 接受客户端连接请求if(c_fd == -1){printf("接受客户端连接请求失败\n");}printf("客户端连接成功!\n");												// 输出客户端地址printf("客户端地址:%s:%d\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));if(fork() == 0){                                                          // 子进程处理客户端请求if(fork() == 0){                                                       // 孙子进程处理客户端请求 while(1){memset(&writeBuf, 0, sizeof(writeBuf));                         // 写出缓冲区清零printf("请输入要发送的数据:\n");                                   // 输出提示信息 fgets(writeBuf, sizeof(writeBuf), stdin);                        // 读入用户输入  //ssize_t write(int fd, const void *buf, size_t count);n_write = write(c_fd, writeBuf, sizeof(writeBuf));        		// 写出数据if(n_write == -1){printf("发送数据失败\n");}else{printf("发送数据:%s,发送字节数:%d\n", writeBuf, n_write);      // 输出发送的数据}}}while(1){memset(&readBuf, 0, sizeof(readBuf));                                 // 读入缓冲区清零//ssize_t read(int fd, void *buf, size_t count);n_read = read(c_fd, readBuf, sizeof(readBuf));                 // 读入数据if(n_read == -1){printf("读取数据失败\n");perror("read");                                   // 输出错误信息}else{printf("接收字节数:%d\n",n_read);                    // 输出接收到的数据printf("接收数据:%s\n", readBuf);                     // 输出接收到的数据}}break;                                                                // 子进程退出}}return 0;
}
/*client.c*/
#include <stdio.h>              //包含标准输入输出头文件
#include <sys/types.h>          //包含系统数据类型头文件
#include <sys/socket.h>          //包含套接字头文件 
#include <netinet/in.h>         //包含IPv4头文件 
#include <arpa/inet.h>          //包含网络地址转换头文件 
#include <stdlib.h>              //包含标准库头文件 
#include <string.h>              //包含字符串头文件 
#include <unistd.h>              //包含unistd头文件 
#include <errno.h>               //包含错误号头文件 int main(int argc, char **argv)
{int c_fd;                                               //客户端套接字文件描述符int n_write;                                            //写入字节数int n_read;                                             // 读入字节数    char sendBuf[128] = {0};                        //发送数据缓冲区char readBuf[128];                                       // 读入数据缓冲区                                                          struct sockaddr_in client_addr;                         //客户端地址结构体memset(&client_addr, 0, sizeof(client_addr));           //客户端地址结构体清零if(argc != 3){                                           //参数个数不正确printf("参数错误,请按照格式输入:./client IP地址 端口号\n");exit(-1);}//int socket(int domain, int type, int protocol);c_fd = socket(AF_INET, SOCK_STREAM, 0);                 //创建TCP/IP套接字if(c_fd == -1){printf("创建套接字失败\n");                             perror("socket");exit(-1);}client_addr.sin_family = AF_INET;                       // 设置客户端地址族为IPv4client_addr.sin_port   = htons(atoi(argv[2]));           //设置客户端端口号inet_aton(argv[1], &client_addr.sin_addr);;              //设置客户端IP地址//int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);int ret = connect(c_fd, (struct sockaddr *)&client_addr, sizeof(struct sockaddr));  //连接服务器if(ret == -1){printf("连接服务器失败\n");perror("connect");exit(-1);}printf("连接服务器成功\n");while(1){if(fork() == 0){                                                            //子进程发送数据  while(1){memset(&sendBuf, 0, sizeof(sendBuf));                                //清空发送数据缓冲区printf("请输入要发送的数据:\n");fgets(sendBuf, sizeof(sendBuf), stdin);                                //从控制台获取输入数据//ssize_t write(int fd, const void *buf, size_t count);n_write = write(c_fd, sendBuf, sizeof(sendBuf));                     //发送数据if(n_write == -1){printf("发送数据失败\n");}else{printf("发送数据成功,共发送%d字节数据\n", n_write);printf("发送的数据为:%s\n", sendBuf);}}}while(1){memset(&readBuf, 0, sizeof(readBuf));                                    //清空读入数据缓冲区// ssize_t read(int fd, void *buf, size_t count);n_read = read(c_fd, &readBuf, sizeof(readBuf));                          //接收数据if(n_read == -1){printf("接收数据失败\n");}else{printf("接收数据成功,共接收%d字节数据\n", n_read);printf("接收到的数据为:%s\n", readBuf);}}}// close(c_fd);                                            //关闭套接字  return 0;
}

5.2 实现多个客户端接入服务器通信:

/*server2.c*/
#include <stdio.h>          // 包含标准输入输出头文件
#include <sys/types.h>      // 包含系统数据类型头文件
#include <sys/socket.h>     // 包含系统套接字库的头文件
#include <stdlib.h>         // 包含标准库头文件
#include <arpa/inet.h>      // 包含网络地址转换头文件
#include <netinet/in.h>     // 包含IPv4地址头文件
#include <string.h>         // 包含字符串头文件
#include <unistd.h>         // 包含unistd.h头文件int main(int argc, char **argv)
{int s_fd;                                                                  // 套接字文件描述符int c_fd;                                                                  // 客户端套接字文件描述符int n_read;                                                                // 读入字节数int n_write;                                                               // 写出字节数int mark = 0;                                                               // 标记char readBuf[128] = {0};                                                   // 读入缓冲区char writeBuf[128] = {0};                                                   // 写出缓冲区   struct sockaddr_in server_addr;                                             // 服务器地址结构体struct sockaddr_in client_addr;                                             // 客户端地址结构体memset(&server_addr, 0, sizeof(server_addr));                                // 服务器地址结构体清零memset(&client_addr, 0, sizeof(client_addr));                                // 客户端地址结构体清零if(argc != 3){                                                               // 参数检查    printf("参数错误!请按照格式输入:./server IP地址 端口号\n");exit(-1);}//int socket(int domain, int type, int protocol);s_fd = socket(AF_INET, SOCK_STREAM, 0);                                     // 创建TCP/IP套接字if(s_fd == -1){printf("创建套接字失败!\n");perror("socket");                                                       // 输出错误信息exit(-1);}server_addr.sin_family = AF_INET;                                           // 设置服务器地址族为IPv4server_addr.sin_port   = htons(atoi(argv[2]));                              // 设置服务器端口号//inet_aton("127.0.0.1", &server_addr.sin_addr); inet_aton(argv[1], &server_addr.sin_addr);                                  // 设置服务器IP地址//int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);int ret = bind(s_fd,(struct sockaddr *)&server_addr,sizeof(server_addr));   // 绑定服务器地址if(ret == -1){printf("绑定服务器地址失败\n");perror("bind");                                                         // 输出错误信息exit(-1);}//int listen(int sockfd, int backlog);ret = listen(s_fd, 10);                                                      // 监听套接字if(ret == -1){printf("监听套接字失败\n");perror("listen");                                                        // 输出错误信息exit(-1);}printf("服务器启动成功!\n");int client_addr_len = sizeof(client_addr);                                    // 客户端地址长度//int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);while(1){printf("等待客户端连接...\n");c_fd = accept(s_fd, (struct sockaddr *)&client_addr, &client_addr_len);       // 接受客户端连接请求if(c_fd == -1){printf("接受客户端连接请求失败\n");}printf("客户端连接成功!\n");												// 输出客户端地址printf("客户端地址:%s:%d\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));mark++;                                                                        // 标记  if(fork() == 0){                                                              // 子进程处理客户端请求if(fork() == 0){                                                           // 孙子进程处理客户端请求 while(1){sprintf(writeBuf, "Welcome NO.%dclient\n", mark);                            // 构造数据//ssize_t write(int fd, const void *buf, size_t count);n_write = write(c_fd, writeBuf, sizeof(writeBuf));                           // 写出数据if(n_write == -1){printf("发送数据失败\n");}else{printf("发送数据:%s,发送字节数:%d\n", writeBuf, n_write);              // 输出发送的数据}sleep(3);                                                                     // 休眠3秒    }}while(1){memset(&readBuf, 0, sizeof(readBuf));                                       // 读入缓冲区清零//ssize_t read(int fd, void *buf, size_t count);n_read = read(c_fd, readBuf, sizeof(readBuf));                                // 读入数据if(n_read == -1){printf("读取数据失败\n");perror("read");                                                         // 输出错误信息}else{printf("接收字节数:%d\n",n_read);                                        // 输出接收到的数据printf("接收数据:%s\n", readBuf);                                       // 输出接收到的数据}}break;                                                                          // 子进程退出}}return 0;
}
/*client.c*/
#include <stdio.h>              //包含标准输入输出头文件
#include <sys/types.h>          //包含系统数据类型头文件
#include <sys/socket.h>          //包含套接字头文件 
#include <netinet/in.h>         //包含IPv4头文件 
#include <arpa/inet.h>          //包含网络地址转换头文件 
#include <stdlib.h>              //包含标准库头文件 
#include <string.h>              //包含字符串头文件 
#include <unistd.h>              //包含unistd头文件 
#include <errno.h>               //包含错误号头文件 int main(int argc, char **argv)
{int c_fd;                                               //客户端套接字文件描述符int n_write;                                            //写入字节数int n_read;                                             // 读入字节数    char sendBuf[128] = {0};                        //发送数据缓冲区char readBuf[128];                                       // 读入数据缓冲区                                                          struct sockaddr_in client_addr;                         //客户端地址结构体memset(&client_addr, 0, sizeof(client_addr));           //客户端地址结构体清零if(argc != 3){                                           //参数个数不正确printf("参数错误,请按照格式输入:./client IP地址 端口号\n");exit(-1);}//int socket(int domain, int type, int protocol);c_fd = socket(AF_INET, SOCK_STREAM, 0);                 //创建TCP/IP套接字if(c_fd == -1){printf("创建套接字失败\n");                             perror("socket");exit(-1);}client_addr.sin_family = AF_INET;                       // 设置客户端地址族为IPv4client_addr.sin_port   = htons(atoi(argv[2]));           //设置客户端端口号inet_aton(argv[1], &client_addr.sin_addr);;              //设置客户端IP地址//int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);int ret = connect(c_fd, (struct sockaddr *)&client_addr, sizeof(struct sockaddr));  //连接服务器if(ret == -1){printf("连接服务器失败\n");perror("connect");exit(-1);}printf("连接服务器成功\n");while(1){if(fork() == 0){                                                            //子进程发送数据  while(1){memset(&sendBuf, 0, sizeof(sendBuf));                                //清空发送数据缓冲区printf("请输入要发送的数据:\n");fgets(sendBuf, sizeof(sendBuf), stdin);                                //从控制台获取输入数据//ssize_t write(int fd, const void *buf, size_t count);n_write = write(c_fd, sendBuf, sizeof(sendBuf));                     //发送数据if(n_write == -1){printf("发送数据失败\n");}else{printf("发送数据成功,共发送%d字节数据\n", n_write);printf("发送的数据为:%s\n", sendBuf);}}}while(1){memset(&readBuf, 0, sizeof(readBuf));                                    //清空读入数据缓冲区// ssize_t read(int fd, void *buf, size_t count);n_read = read(c_fd, &readBuf, sizeof(readBuf));                          //接收数据if(n_read == -1){printf("接收数据失败\n");}else{printf("接收数据成功,共接收%d字节数据\n", n_read);printf("接收到的数据为:%s\n", readBuf);}}}// close(c_fd);                                            //关闭套接字  return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/855257.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C语言程序设计-10 指针

指针是&#xff23;语言中广泛使用的一种数据类型。运用指针编程是&#xff23;语言最主要的风格之一。利用指针变量可以表示各种数据结构&#xff1b;能很方便地使用数组和字符串&#xff1b;并能象汇编语言一样 处理内存地址&#xff0c;从而编出精练而高效的程序。指针极大地…

C语言 指针——字符数组与字符指针:字符串的输入和输出

目录 逐个字符输入输出字符串 整体输入输出字符串 用scanf输入/输出字符串 用gets输入/输出字符串 用scanf输入/输出字符串 用gets输入/输出字符串 逐个字符输入输出字符串 #define STR_LEN 80 char str[STR_LEN 1 ]; 整体输入输出字符串 用scanf输入/输出字符串 用gets…

【CVPR2021】LoFTR:基于Transformers的无探测器的局部特征匹配方法

LoFTR&#xff1a;基于Transformers的局部检测器 0. 摘要 我们提出了一种新的局部图像特征匹配方法。我们建议先在粗略级别建立像素级密集匹配&#xff0c;然后再在精细级别细化良好匹配&#xff0c;而不是按顺序进行图像特征检测、描述和匹配。与使用成本体积搜索对应关系的密…

oracle12c到19c adg搭建(二)oracle12c数据库软件安装

运行安装程序 不勾选 只安装软件 选择单实例安装 选择语言 企业版 确认目录 产品目录 用户组 开始安装 执行root脚本 [rooto12u19p software]# /u01/app/oraInventory/orainstRoot.sh Changing permissions of /u01/app/oraInventory. Adding read,write permissions for gro…

字节豆包大模型API吞吐、函数调用能力、长上下文能力测试总结

离开模型能力谈API价格都是耍流氓&#xff0c;豆包大模型作为API最便宜的模型之一&#xff0c;最近向个人开发者开放了&#xff0c;花了300元和一些时间对模型的API吞吐、函数调用能力、长上下文能力等进行了深度测试&#xff0c;看看它的能力究竟适合做 AI 应用开发吗&#xf…

【Anaconda】【Windows编程技术】【Python】Anaconda的常用命令及实操

一、Anaconda终端 在安装Anaconda后&#xff0c;电脑上会新增一个Anaconda终端&#xff0c;叫Anaconda Prompt&#xff0c;如下图&#xff1a; 我们选择“打开文件位置”&#xff0c;将快捷方式复制一份到桌面上&#xff0c;这样日后就可以从桌面上方便地访问Anaconda终端了&…

用python实现多文件多文本替换功能

用python实现多文件多文本替换功能 今天修改单位项目代码时由于改变了一个数据结构名称&#xff0c;结果有几十个文件都要修改&#xff0c;一个个改实在太麻烦&#xff0c;又没有搜到比较靠谱的工具软件&#xff0c;于是干脆用python手撸了一个小工具&#xff0c;发现python在…

微服务中的相关概念

Eureka Eureka 是由 Netflix 开发的一个服务发现和注册中心&#xff0c;广泛应用于微服务架构中。Eureka 主要用于管理和协调分布式服务的注册和发现&#xff0c;确保各个服务之间能够方便地找到并通信。它是 Netflix OSS&#xff08;Netflix Open Source Software&#xff09…

C#心跳机制客户端

窗体&#xff08;richTextBox右显示聊天&#xff09; 步骤 点击链接按钮 tcpclient客户端步骤 1创建客户端对象 2连接服务器connect 3创建网络基础流发消息 .write发消息 4 创建网络基础流接消息 .read接消息 5 断开连接…

python库离线安装方法(pyqt5离线安装方法)

在某些情况下&#xff0c;我们的计算机是无法联网的。 网上大部分方法&#xff1a; 这些方法都有个问题&#xff0c;就是库是需要依赖其它库的&#xff0c;你不知道它需要依赖什么库&#xff0c;就是提供了依赖库的列表也麻烦&#xff0c;依赖库也是有对应版本要求的&#xf…

自制调色小工具给图片加滤镜,修改图片红、绿、蓝通道及亮度,修改图片颜色

上篇&#xff1a; 上篇我们给地图添加了锐化、模糊等滤镜&#xff0c;这篇来写一个小工具给图片调色。 调色比锐化等滤镜要简单许多&#xff0c;直接拿到像素值修改即可。不需要用到卷积核。。。(*^▽^*) 核心原理就是图像结构&#xff0c;使用context.getImageData获取图像像…

cad怎么转成pdf文件?方法很简单!

cad怎么转成pdf文件&#xff1f;在数字化时代&#xff0c;CAD图纸的转换与共享已成为日常工作中的常态。无论是建筑设计师、工程师还是学生&#xff0c;都可能遇到需要将CAD文件转换为PDF格式的需求。本文将为您推荐三款高效的CAD转PDF软件&#xff0c;让您轻松实现文件格式的转…

C++ 48 之 继承的基本语法

#include <iostream> #include <string> using namespace std;// 定义一个基类&#xff0c;把公共的部分写在这里&#xff0c;以后让别的类继承即可 class BasePage{ public:void header(){cout << "公共的头部"<< endl;}void footer(){cout…

STM32单片机-BKP和RTC

STM32单片机-BKP和RTC 一、Unix时间戳1.1 时间戳转换 二、BKP(备份寄存器)三、RTC(实时时钟)3.1 RTC工作原理 四、代码部分4.1 BKP备份寄存器4.2 RTC实时时钟 一、Unix时间戳 Unix时间戳定义为从伦敦时间的1970年1月1日0时0分0秒开始所经过的秒数&#xff0c;不考虑闰秒时间戳…

vue3使用echarts简单教程~~概念篇

没写过 写着玩玩 不足的地方还望小伙伴补充~~ 概念篇 文档奉上&#xff1a;数据集 - 概念篇 - 使用手册 - Apache EChartshttps://echarts.apache.org/handbook/zh/concepts/dataset <template><div id"main" style"width: 600px; height: 400px&…

集合进阶:增强for循环和lambda表达式

一.增强for遍历 1.增强for的底层是迭代器,为了简化迭代器的代码书写的。 2.他是JDK5之后出现的,其内部原理就是一个lterrator迭代器。 3.所有的单列集合和数组才能用增强for进行遍历 二.格式 for(元素的数据类型 变量名;数组或者集合){} 三.代码 Collection<String>…

72-UDP协议工作原理及实战

#ifndef UDPCOMM_H #define UDPCOMM_H#include <QMainWindow> #include <QUdpSocket> // 用于发送和接收UDP数据报 #include <QtNetwork>QT_BEGIN_NAMESPACE namespace Ui { class udpComm; } QT_END_NAMESPACEclass udpComm : public QMainWindow {Q_OBJECT…

定个小目标之刷LeetCode热题(23)

今天写这道题&#xff0c;背过八股文的都应该知道LRU算法缓存的基本原理&#xff0c;在 Java 语言中&#xff0c;同样有类似的数据结构 LinkedHashMap&#xff0c;本题我们采用自己创建哈希表双链表的形式简单实现一下 对于get操作&#xff1a;通过cache.get(key)获取&#xff…

格雷母线定位与控制系统:确保机车平稳运行的关键

微深节能的格雷母线高精度位移检测系统是一种高精度的位置检测设备&#xff0c;它通过发射和接收信号来确定移动物体的实时位置。在机车定位系统中&#xff0c;格雷母线被安装在固定的轨道上&#xff0c;而机车上的检测装置则负责读取这些信号&#xff0c;从而准确计算出机车的…

一文弄懂 Python os.walk(),轻松搞定文件处理和目录遍历

&#x1f349; CSDN 叶庭云&#xff1a;https://yetingyun.blog.csdn.net/ Python os 模块的 walk() 方法以自顶向下或自底向上的方式遍历指定的目录树&#xff0c;从而显示目录树中的文件名。对于目录树中的每个目录&#xff0c;os.walk() 方法都会产生一个包含目录路径、当前…