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 int
    
uint32_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 int
    
uint32_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 			函数返回值,成功返回非负套接字描述符,失败返回-1
    
int 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:
				数据报套接字定义了一种无连接的服,数据通过相互独立的报文进行传输,是无序的,并且不保证是可靠,无差错的。它使用					数据报协议UDP
				3. 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,如果失败则返回-1
    
int 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,如果失败则返回-1
    
int 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,如果失败则返回-1
    
int 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			函数返回值,如果成功则返回实际发送的字节数,如果失败则返回-1
    
int 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			函数返回值,如果成功则返回实际发送的字节数,失败则返回-1
    
int 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;                                           // 设置服务器地址族为IPv4
    server_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;                       // 设置客户端地址族为IPv4
    client_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;                                           // 设置服务器地址族为IPv4
    server_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;                       // 设置客户端地址族为IPv4
    client_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;
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Kantana和The Sandbox联手打造元宇宙娱乐的未来

The Sandbox 是一个开创性的元宇宙、游戏和创作平台&#xff0c;泰国领先的娱乐公司 Kantana 很高兴地宣布双方将建立合作关系&#xff0c;共同打造元宇宙娱乐的未来。 此次合作结合了 Kantana 引以为傲的故事讲述专长和The Sandbox 的用户生成内容 (UGC) 工具&#xff0c;创建…

若依框架自定义开发使用学习笔记(1)

因为我是跳着学的&#xff0c;原理那些都没咋看。 代码自动生成&#xff0c;依赖sql表 在ruoyi数据库中&#xff0c;创建你想要的表&#xff0c;这里我创建了个购物车表&#xff0c;由于空间有限&#xff0c;只能拍到这么多。 然后就可以在前端自动生成代码 点击导入按钮 …

家庭财务新助手,记录收支明细,一键导出表格,让您的家庭财务一目了然!

在繁忙的现代生活中&#xff0c;家庭财务管理常常成为一项令人头疼的任务。如何记录每一笔收支&#xff0c;如何清晰地掌握家庭财务状况&#xff0c;如何合理规划未来开支&#xff0c;这些都是我们需要面对的问题。然而&#xff0c;有了这款家庭财务助手——晨曦记账本&#xf…

入侵检测系统(IDS)

入侵检测 入侵检测&#xff08;Intrusion Detection&#xff09;是指发现或确定入侵行为存在或出现的动作&#xff0c;也就是发现、跟踪并记录计算机系统或计算机网络中的非授权行为&#xff0c;或发现并调查系统中可能为视图入侵或病毒感染所带来的异常活动。 入侵检测系统 …

【案例分析】一文讲清楚SaaS产品运营的六大杠杆是什么?具体怎么运用?

在SaaS&#xff08;软件即服务&#xff09;行业&#xff0c;如何快速获取用户并实现持续增长一直是企业关注的重点。近年来&#xff0c;分销裂变策略因其高效性和低成本特性&#xff0c;成为许多SaaS企业实现快速增长的秘诀。下面&#xff0c;我们将通过一个具体的案例来剖析成…

大语言模型的昨天、今天和明天

引言 近年来&#xff0c;人工智能&#xff08;AI&#xff09;技术突飞猛进&#xff0c;其中大语言模型&#xff08;LLM&#xff09;无疑是最引人瞩目的技术之一。从OpenAI的GPT系列到Meta的Llama模型&#xff0c;大语言模型的发展不仅改变了人们对AI的认知&#xff0c;也在各行…

智慧体育场馆:视频孪生引领体育场馆智能化

随着数字经济时代的发展&#xff0c;技术的迭代跃迁加速了体育场馆运营革新的步调&#xff0c;在技术赋能理念的驱动下&#xff0c;体育场馆逐步由复合化发展姿态&#xff0c;升级为物联感知式的智能场馆&#xff0c;并迈向了智慧体育场馆的发展之路。《“十四五”时期全民健身…

怎么移除pdf文件编辑限制,有哪些方法?

PDF是我们在学习或工作中常常应用到的一种文件格式&#xff0c;因为它的跨平台性和文档保真度而备受欢迎。但是&#xff0c;有时我们会遇到PDF编辑权限被限制了&#xff0c;那么pdf解除编辑限制可以用什么方法呢&#xff1f;别急&#xff0c;接下来&#xff0c;本文将深入探讨如…

头歌资源库(12)找第K小数

一、 问题描述 二、算法思想 可以使用快速排序算法来解决这个问题。 首先&#xff0c;选择一个基准元素&#xff0c;通常选择序列的第一个元素。 然后&#xff0c;将序列中小于等于基准元素的元素放在基准元素的左边&#xff0c;大于基准元素的元素放在基准元素的右边。 接着…

哪里可以姓名设计免费签名?6个软件帮助你轻松设计签名

哪里可以姓名设计免费签名&#xff1f;6个软件帮助你轻松设计签名 这里有六个免费的软件和在线工具可以帮助您设计个性化的签名&#xff1a; 1.一键logo设计&#xff1a;这是一个功能强大且易于使用的设计工具&#xff0c;提供了丰富的签名设计模板和各种字体效果供选择。您可…

如何配置node.js环境

文章目录 step1. 下载node.js安装包step2. 创建node_global, node_cache文件夹step3.配置node环境变量step3. cmd窗口检查安装是否成功step4. 设置缓存路径和全局安装路径step5. 下载指令cnpm step1. 下载node.js安装包 下载地址&#xff1a;node.js 我的电脑时windows系统、6…

RX8900/INS5A8900实时时钟-国产兼容RS4TC8900

该模块是一个符合I2C总线接口的实时时钟&#xff0c;包括一个32.768 kHz的DTCXO。 除了提供日历&#xff08;年、月、日、日、时、分、秒&#xff09;功能和时钟计数器功能外&#xff0c;该模块还提供了大量其他功能&#xff0c;包括报警功能、唤醒定时器功能、时间更新中断功能…

c#音乐播放器续(联网下载)

音乐播放器 0.前言1.关于本地音乐播放2.使用iTunes Search API进行联网下载歌曲2.1 控件2.2 函数实现2.2.1 控件2&#xff1a;搜索歌曲2.2.2 控件3&#xff1a;下载歌曲 2.3 主界面 3.拓展 0.前言 书接上文&#xff0c;我们已经实现了一个能够播放本地音乐的音乐播放器&#x…

系统架构——Spring Framework

目录 &#xff08;1&#xff09;基本介绍 &#xff08;2&#xff09;基本发展历史 &#xff08;3&#xff09;了解和学习 Spring 4.x 系列的系统架构 1、第一个模块&#xff1a;做核心容器&#xff08;Core Contaner&#xff09; 2、第二个模块&#xff1a;AOP与Aspects(这…

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

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

SCI绘图【1】-不同颜色表示密度和差异--密度图

参考资料&#xff1a;密度图&#xff08;Density Plot&#xff09; - 数据可视化图表 - 数字孪生百科 密度图是快速观察变量数值分布的有效方法之一。通常情况下&#xff0c;会根据两个变量将平面绘图区域分为非常多的子区域&#xff0c;之后以不同颜色表示落在该区域上样本的…

VBA技术资料MF161:按需要显示特定工作表

我给VBA的定义&#xff1a;VBA是个人小型自动化处理的有效工具。利用好了&#xff0c;可以大大提高自己的工作效率&#xff0c;而且可以提高数据的准确度。“VBA语言専攻”提供的教程一共九套&#xff0c;分为初级、中级、高级三大部分&#xff0c;教程是对VBA的系统讲解&#…

ELISA Flex: Monkey IFN-γ (HRP)

ELISA Flex: Monkey IFN-γ (HRP)该ELISA试剂盒能够检测溶液样本比如细胞培养上清或者血清/血浆中猴子γ干扰素&#xff08;IFN-γ&#xff09;的含量。 产品组分&#xff1a; 捕获抗体&#xff1a;克隆号MT126L&#xff08;0.5mg/ml&#xff09; 检测抗体&#xff1a;克隆号7…

如何开发海外仓系统?开发和购买海外仓系统哪个性价比更高

每个海外仓都各自不同&#xff0c;具备的优势&#xff0c;面临的困境也完全不同。所以在对海外仓系统的讨论时&#xff0c;是自己开发还是购买&#xff0c;都要放到具体的海外仓环境上才能得出合理结论。 1、实现海外仓精细化管理&#xff0c;你有哪些选择&#xff1f; 选择成…