当前位置: 首页 > news >正文

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;
}

相关文章:

Linux系统编程——网络编程

目录 一、对于Socket、TCP/UDP、端口号的认知&#xff1a; 1.1 什么是Socket&#xff1a; 1.2 TCP/UDP对比&#xff1a; 1.3 端口号的作用&#xff1a; 二、字节序 2.1 字节序相关概念&#xff1a; 2.2 为什么会有字节序&#xff1a; 2.3 主机字节序转换成网络字节序函数…...

信息安全技术基础知识-经典题目

【第1题】 1.在信息安全领域&#xff0c;基本的安全性原则包括机密性(Confidentiality)、完整性(Integrity)和 可用性(Availability)。机密性指保护信息在使用、传输和存储时 (1) 。信息加密是保证系统机密性的常用手段。使用哈希校验是保证数据完整性的常用方法。可用性指保证…...

nextjs(持续学习中)

return ( <p className{${lusitana.className} text-xl text-gray-800 md:text-3xl md:leading-normal}> Welcome to Acme. This is the example for the{’ } Next.js Learn Course , brought to you by Vercel. ); } 在顶级 /public 文件夹下提供静态资产 **默认 /…...

数据预处理与特征工程、过拟合与欠拟合

数据预处理与特征工程 常用的数据预处理步骤 向量化&#xff1a;将数据转换成pytorch张量值归一化&#xff1a;将特定特征的数据表示成均值为0&#xff0c;标准差为1的数据的过程&#xff1b;取较小的值&#xff1a;通常在0和1之间&#xff1b;相同值域处理缺失值特征工程&am…...

甲辰年五月十四风雨思

甲辰年五月十四风雨思 夜雨消暑气&#xff0c;远光归家心。 ​只待万窗明&#xff0c;朝夕千家勤。 ​苦乐言行得&#xff0c;酸甜日常品。 宫商角徵羽&#xff0c;​仁义礼智信。...

java分别使用 iText 7 库和iText 5 库 将excel转成PDF导出,以及如何对excel转PDF合并单元格

第一种 package com.junfun.pms.report.util;import com.itextpdf.kernel.font.PdfFontFactory; import com.itextpdf.layout.Document; import com.itextpdf.layout.element.Paragraph; import com.itextpdf.layout.property.TextAlignment; import com.itextpdf.layout.prop…...

Java特性之设计模式【访问者模式】

一、访问者模式 概述 在访问者模式&#xff08;Visitor Pattern&#xff09;中&#xff0c;我们使用了一个访问者类&#xff0c;它改变了元素类的执行算法。通过这种方式&#xff0c;元素的执行算法可以随着访问者改变而改变。这种类型的设计模式属于行为型模式。根据模式&…...

【教师资格证考试综合素质——法律专项】未成年人保护法笔记以及练习题

《中华人民共和国未成年人保护法》 目录 第一章 总 则 第二章 家庭保护 第三章 学校保护 第四章 社会保护 第五章 网络保护 第六章 政府保护 第七章 司法保护 第八章 法律责任 第九章 附 则 介一&#xff0e;首次颁布&#xff1a;第一部《中华人民共和国未成年人保护法…...

6.19作业

TCP服务器 #include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <unistd.h> #include <arpa/inet.h> #include <netinet/in.h> #include <string.h>#define PORT 8888 #define IP "192.168.124.39&q…...

java 线程之间通信-volatile 和 synchronized

你好&#xff0c;我是 shengjk1&#xff0c;多年大厂经验&#xff0c;努力构建 通俗易懂的、好玩的编程语言教程。 欢迎关注&#xff01;你会有如下收益&#xff1a; 了解大厂经验拥有和大厂相匹配的技术等 希望看什么&#xff0c;评论或者私信告诉我&#xff01; 文章目录 一…...

资源宝库网站!人人必备的神器!

面对网络中海量的内容&#xff0c;一个高效、便捷的网络导航工具&#xff0c;可以帮助我们快速查找使用网络资源。无论是职场精英还是学生党&#xff0c;使用导航网站都可以帮助我们提升效率。下面小编就来和大家分享一款资源宝库网站-办公人导航-实用的办公生活导航网站&#…...

Redis实战—优惠卷秒杀(锁/事务/代理对象的应用)

本博客为个人学习笔记&#xff0c;学习网站与详细见&#xff1a;黑马程序员Redis入门到实战 P50 - P54 目录 优惠卷秒杀下单功能实现 超卖问题 悲观锁与乐观锁 实现CAS法乐观锁 一人一单功能实现 代码优化 代码细节分析 优惠卷秒杀下单功能实现 ​ ​ Controller层…...

HTML星空特效

目录 写在前面 完整代码 代码分析 运行效果 系列文章 写在后面 写在前面 100行代码实现HTML星空特效。 完整代码 全部代码如下。 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"&g…...

银行数仓项目实战(四)--了解银行业务(存款)

文章目录 项目准备存款活期定期整存整取零存整取存本取息教育储蓄定活两便通知存款 对公存款对公账户协议存款 利率 项目准备 &#xff08;贴源层不必写到项目文档&#xff0c;因为没啥操作没啥技术&#xff0c;只是数据。&#xff09; 可以看到&#xff0c;银行的贴源层并不紧…...

MySQL版本发布模型

MySQL 8.0 之后使用了新的版本控制和发布模型&#xff0c;分为两个主线&#xff1a;长期支持版&#xff08;LTS&#xff09;以及创新版。这两种版本都包含了缺陷修复和安全修复&#xff0c;都可以用于生产环境。 下图是 MySQL 的版本发布计划&#xff1a; 长期支持版 MySQL…...

java: 不兼容的类型: org.apache.xmlbeans.XmlObject无法转换为x2006.main.CTRow

我使用的xmlbeans版本是5.0&#xff0c;使用xmlbeans包做转换时&#xff0c;报错&#xff0c;正如标题显示得那样 解决办法 额外再引入下面的jar包 <dependency><groupId>org.apache.xmlbeans</groupId><artifactId>xmlbeans</artifactId><…...

内容时代:品牌如何利用社交平台精准触达用户

还记得学生时代老师教写作文的时候常说的一句话就是“开头质量决定了阅卷老师想不想花精力去读&#xff0c;而内容质量决定了她愿不愿意给你判高分”这个世界仿若一个巨大的圆&#xff0c;同样的逻辑放在任何地方好像都能适用。在品牌营销中&#xff0c;内容已成为品牌与消费者…...

推荐4款PC端黑科技工具,快来看看,建议收藏

Thunderbird Thunderbird 是由 Mozilla 基金会开发的一款免费且开源的电子邮件客户端&#xff0c;支持 Windows、macOS、Linux 等多种操作系统。它不仅可以用于发送和接收电子邮件&#xff0c;还可以作为新闻阅读器、聊天工具以及日历应用。 Thunderbird 提供了丰富的功能&…...

汉化版PSAI全面测评,探索国产AI绘画软件的创新力量

引言 随着AI技术的飞速发展&#xff0c;图像处理和绘画领域迎来了新的变革。作为一名AIGC测评博主&#xff0c;今天我们测评的是一款国产AI绘画软件——StartAI&#xff0c;一句话总结&#xff1a;它不仅在技术上毫不逊色于国际大牌&#xff0c;更在用户体验和本地化服务上做到…...

LeetCode | 709.转换成小写字母

这道题可以用api也可以自己实现&#xff0c;都不难&#xff0c;大小字母之前相差了32&#xff0c;检查到大写字母时加上32即可 class Solution(object):def toLowerCase(self, s):""":type s: str:rtype: str"""return s.lower()class Solution…...

洗地机哪个品牌比较好?四款好用靠谱的优质洗地机推荐

随着现代生活节奏的加快&#xff0c;家庭清洁成了一项耗时且繁琐的任务。洗地机凭借其智能化和高效的清洁能力&#xff0c;越来越受到大家的青睐。然而&#xff0c;市场上各种品牌和型号琳琅满目&#xff0c;让人眼花缭乱。为了帮助大家在众多选择中找到心仪的产品&#xff0c;…...

java:spring actuator添加自定义endpoint

# 项目代码资源&#xff1a; 可能还在审核中&#xff0c;请等待。。。 https://download.csdn.net/download/chenhz2284/89437274 # 项目代码 【pom.xml】 <dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId&…...

LeetCode88-删除有序数组中的重复项

题目 给你一个有序数组 nums &#xff0c;请你 原地 删除重复出现的元素&#xff0c;使得出现次数超过两次的元素只出现两次 &#xff0c;返回删除后数组的新长度。 不要使用额外的数组空间&#xff0c;你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。 代…...

SpringBoot Starter 通用接口加密组件(防篡改)+ RequestBodyAdvice和ResponseBodyAdvice原理

防篡改&#xff1a; 如何保证接口安全&#xff0c;做到防篡改防重放&#xff1f;_接口防止串改-CSDN博客 接口安全设计之防篡改和防重放_接口防篡改机制-CSDN博客 参考博客&#xff1a; RequestBodyAdvice和ResponseBodyAdvice原理详解-CSDN博客 SpringBoot Starter 通用接口…...

delphi 如何使用TEdgeBrowser组件以及打包环境在其他主机上运行

不管开发环境还是第三方环境先安装运行时库&#xff1a;Microsoft Edge WebView2 | Microsoft Edge Developer 开发环境可以直接通过&#xff1a; delphi IDE安装 安装完毕后进入到指定路径&#xff0c;复制里面的WebView2Loader.dll到你要开发的程序根目录&#xff1a; 大致路…...

Sui的Fastcrypto加密库刷新速度记录

Sui使用的加密库Fastcrypto打破了许多速度记录&#xff0c;Mysten Labs在基准测试和安全分析中的工作修复了许多安全漏洞&#xff0c;同时通过识别新的优化技巧为创新开辟了道路。 最近在伦敦帝国理工学院举行的国际性能工程会议&#xff08;ICPE&#xff09;基准测试研讨会上…...

Malformed \uxxxx encoding或Maven server structure problem问题解决

问题描述&#xff1a; idea运行项目时, 报错如下: [ERROR] Malformed \uxxxx encoding. [ERROR] Maven server structure problem [ERROR] Malformed \uxxxx encoding. 解决方法总结 先说一下解决方法无非是下面几种 1、先检查项目的.properties、.yml 、pom.xml、logback等…...

Sui主网升级至V1.27.2版本

其他升级要点如下所示&#xff1a; 重点&#xff1a; #17245 增加了一个新的协议版本&#xff0c;并在开发网络上启用了Move枚举。 JSON-RPC #17245: 在返回的JSON-RPC结果中增加了对Move枚举值的支持。 GraphQL #17245: 增加了对Move枚举值和类型的支持。 CLI #179…...

Cheat Engine 学习

文章目录 Exact Value scanning任务实现步骤Unknown initial value任务实现步骤原理说明Floating points任务实现步骤原理说明Code finder任务实现步骤原理说明Pointers任务实现步骤原理说明Change Pointer 操作:Active(活跃状态)和数值修改:Code Injection任务概述实现步骤…...

【千帆AppBuilder】你有一封邮件待查收|未来的我,你好吗?欢迎体验AI应用《未来信使》

我在百度智能云千帆AppBuilder开发了一款AI原生应用&#xff0c;快来使用吧&#xff01;「未来信使」&#xff1a;https://appbuilder.baidu.com/s/Q1VPg 目录 背景人工智能未来的信 未来信使功能介绍Prompt组件 千帆社区主要功能AppBuilderModelBuilder详细信息 推荐文章 未来…...

免费解析网站制作/兰州网络推广关键词优化

Fortify漏洞之 Privacy Violation&#xff08;隐私泄露&#xff09;和 Null Dereference&#xff08;空指针异常&#xff09;参考文章&#xff1a; &#xff08;1&#xff09;Fortify漏洞之 Privacy Violation&#xff08;隐私泄露&#xff09;和 Null Dereference&#xff08…...

wordpress 繁简转换插件/流量精灵官网

首先需要定义一个调整堆的函数&#xff0c;动态调整堆为最大堆 def heap_sink(heap, heap_size, parent_index):print("heap_sink")print("heap_size", heap_size)print("parent_index", parent_index)"""最大堆-下沉算法"…...

基于java框架的网站开发/站长之家是干什么的

服务注册慢 默认情况下&#xff0c;服务注册到Eureka Server的过程较慢。SpringCloud官方文档中给出了详细的原因 大致含义&#xff1a;服务的注册涉及到心跳&#xff0c;默认心跳间隔为30s。在实例、服务器、客户端都在本地缓存中具有相同的元数据之前&#xff0c;服务不可用…...

苹果网站导航条/广告联盟接单赚钱平台

fetch是一种HTTP数据请求的方式。fetch不是ajax的进一步封装&#xff0c;是原生js。Fetch函数就是原生js&#xff0c;没有使用XMLHttpRequest对象&#xff0c;是一个全局方法。 fetch 规范与 jQuery.ajax() 主要有三种方式的不同&#xff1a; 当接收到一个代表错误的 HTTP 状态…...

90后小姑娘做网站/免费html网页模板

导读&#xff1a; 黑客会在很多开源框架里植入自己的木马或后门&#xff0c;典型的就是eval一句话&#xff0c;如果你使用的是开源程序&#xff0c;或者从别人那里购买回来的程序&#xff0c;一定要认真检查&#xff0c;及时清理掉后门木马&#xff0c;以免网站和服务器被黑客…...

凡科网做的网站在百度上能找到吗/深圳百度关键词

原标题&#xff1a;经验 | 德勤19par面合集楼主新鲜audit par面经验文章来源&#xff1a;应届生求职网哎&#xff0c;平复一下心情&#xff0c;刚刚面试坐在家里&#xff0c;总还保有一丝希望&#xff0c;准备的过程论坛给了很多帮助&#xff0c;来回馈这个神奇的论坛&#xff…...