“掌握Linux网络编程艺术,构建跨平台的网络通信解决方案!“#Linux系统编程之网络编程

"掌握Linux网络编程艺术,构建跨平台的网络通信解决方案!"#Linux系统编程之网络

前言

  本篇博文深入探讨了Linux系统下的网络编程,内容涵盖网络编程的概览、字节序的重要性、socket编程的基本步骤,以及Linux系统提供的丰富网络编程API的简要分析。接下来,我们将逐步展示两个不同实现的socket服务端代码示例,一个socket客户端的代码实现,进而展示如何通过socket实现客户端与服务器之间的双向聊天功能,并最终探讨如何设计一个socket服务器以同时连接并处理多个客户端的通信。
  对于看到这篇博文的朋友,如果您觉得内容有价值,不妨先点个赞,再继续深入阅读,相信您会收获满满!

预备知识

  一、C变量
  二、基本输入输出
  三、流程控制
  四、函数
  五、指针
  六、字符串
  七、结构体
  八、联合体
  九、Linux系统基本操作命令如mkdir,ls -l等。
  十、Linux系统编程之进程的知识

  如果以上知识不清楚,请自行学习后再来浏览。如果我有没例出的,请在评论区写一下。谢谢啦!

一、 网络编程概述

1.1 网络编程概述图

请添加图片描述

1.2 TCP/UDP对比(面试)

  1. TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前 不需 要建立连接
  2. TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付
  3. TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流;UDP是面向报文的 UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如IP电话,实时视频会议等)
  4. 每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信
  5. CP首部开销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
  在Linux网络编程中通常用5000~10000的端口号

二、 字节序

  在Linux系统下,网络编程中的字节序是一个重要的概念,它涉及到多字节数据在计算机内存中存储或在网络中传输时的字节排列顺序。字节序主要分为两种:大端字节序(Big-Endian)小端字节序(Little-Endian)。

2.1 大端字节序(Big-Endian)

  • 定义:·高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。·这种字节序也被称为网络字节序,因为TCP/IP协议栈采用大端字节序作为网络传输的标准。
  • 示例:对于32位整数0x12345678,在大端字节序下,其在内存中的存储顺序为0x12 0x34 0x56 0x78。

2.2 小端字节序(Little-Endian)

  • 定义低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。这种字节序常见于某些CPU架构,如x86系列。
  • 示例:对于同样的32位整数0x12345678,在小端字节序下,其在内存中的存储顺序为0x78 0x56 0x34 0x12。

2.3 字节序的重要性

  在网络编程中,由于不同的计算机可能采用不同的字节序,因此在数据交换时需要进行字节序的转换,以确保数据的正确解析。例如,当一台采用小端字节序的计算机向采用大端字节序的计算机发送数据时,发送方需要将数据从主机字节序(小端)转换为网络字节序(大端),接收方则需要进行相反的转换。

2.4 字节序转换函数

  Linux系统提供了一系列函数用于字节序的转换,这些函数定义在<arpa/inet.h>头文件中:

  • htonl()htons():分别用于将无符号长整型(32位)和无符号短整型(16位)从主机字节序转换为网络字节序。
  • ntohl()ntohs():分别用于将无符号长整型无符号短整型从网络字节序转换回主机字节序。

2.5 字节序示意图

请添加图片描述

三、 socket编程步骤

请添加图片描述

四、 Linux提供的网络编程API简析

4.1 socket函数介绍

4.1.1 socket函数原型
int socket(int domain, int type, int protocol);
4.1.2协议族 (domain)
  • 指明所使用的协议族,通常为 AF_INET,表示互联网协议族(TCP/IP协议族):
    • AF_INET:IPv4因特网域
    • AF_INET6:IPv6因特网域
    • AF_UNIX:Unix域
    • AF_ROUTE:路由套接字
    • AF_KEY:密钥套接字
    • AF_UNSPEC:未指定
4.1.3 套接字类型 (type)
  • 指定socket的类型:
    • SOCK_STREAM
      流式套接字提供可靠的、面向连接的通信流;它使用TCP协议,从而保证了数据传输的正确性和顺序性。
    • SOCK_DGRAM
      数据报套接字定义了一种无连接的服务,数据通过相互独立的报文进行传输,是无序的,并且不保证是可靠、无差错的。它使用数据报协议UDP
    • SOCK_RAW
      允许程序使用低层协议,原始套接字允许对底层协议如IPICMP进行直接访问,功能强大但使用较为不便,主要用于一些协议的开发。
4.1.4 协议 (protocol)
  • 通常赋值 “0”
  • 0 选择 type 类型对应的默认协议:
  • IPPROTO_TCP:TCP传输协议
  • IPPROTO_UDP:UDP传输协议
  • IPPROTO_SCTP:SCTP传输协议
  • IPPROTO_TIPC:TIPC传输协议

4.2 bind函数介绍

  bind()函数用于将特定的IP地址和端口号与给定的套接字(socket)描述符关联起来。这是网络通信中,特别是在使用TCP/IP协议进行服务器编程时的一个重要步骤。

4.2.1 bind函数原型
#include <sys/types.h>
#include <sys/socket.h>

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
4.2.2 bind函数参数
  • sockfd:是一个socket描述符,它是通过之前调用socket()函数获得的。
  • addr:是一个指向包含有本机IP地址及端口号等信息的sockaddr类型的指针。这个指针指向要绑定给sockfd的协议地址结构。这个地址结构根据创建socket时的地址协议族(如IPv4或IPv6)的不同而不同。
  • addrlen:是addr参数所指向的地址结构的长度,这个长度可以通过sizeof运算符获得。
4.2.3 addr结构体详解
// sockaddr 结构体是一个通用的套接字地址结构体,用于存储各种协议的地址信息
// IPv4对应的是sockaddr_in结构体,它是sockaddr的一个特例

// sockaddr结构体的定义(简化版,非标准实现,仅用于说明)
struct sockaddr {
    unsigned short sa_family; // 协议族,如AF_INET表示IPv4
    char sa_data[14];         // IP地址和端口号等数据,实际使用中会被sockaddr_in等结构体替代
                              // 注意:这里的sa_data数组大小仅为示例,实际定义中并不存在
};

// sockaddr_in结构体,用于表示IPv4地址
struct sockaddr_in {
    sa_family_t sin_family;   // 协议族,AF_INET表示IPv4
    in_port_t sin_port;       // 端口号,网络字节序
    struct in_addr sin_addr;  // IP地址结构体
    unsigned char sin_zero[8];// 填充,无实际意义,仅用于与sockaddr结构体在内存中对齐
                              // 这样sockaddr_in和sockaddr结构体之间才能相互转换
};
//struct in_addr 结构体内容
struct in_addr {
        __be32  s_addr;  网络IP地址
};


// 示例:如何设置sockaddr_in结构体
// 注意:在实际编程中,不会直接对sockaddr_in的sin_addr成员进行赋值,而是会通过
// inet_pton()等函数将点分十进制的IP地址字符串转换为in_addr结构体
// 下面的代码仅为展示结构体成员,不代表实际可运行的赋值操作
struct sockaddr_in addr;
addr.sin_family = AF_INET;       // 设置协议族为IPv4
addr.sin_port = htons(12345);    // 设置端口号为12345,htons()用于主机字节序到网络字节序的转换
// 假设我们有一个点分十进制的IP地址字符串"192.168.1.1",则需要转换后赋值给sin_addr
// 但这里仅展示如何声明和初始化sockaddr_in结构体,不涉及具体的转换操作

4.3 地址转换API

4.3.1 inet_aton函数介绍

  inet_aton 函数是一个用于将点分十进制的IPv4地址字符串(如"192.168.1.123")转换为网络字节序的二进制IP地址形式的函数。这个函数在处理IPv4地址时非常有用,特别是在需要将IP地址作为套接字编程的一部分时。

  函数原型

#include <arpa/inet.h>

int inet_aton(const char *straddr, struct in_addr *addrp);
  • 参数

    • const char *straddr:指向点分十进制IP地址字符串的指针。
    • struct in_addr *addrp:指向in_addr结构体的指针,该结构体用于存储转换后的网络字节序的IP地址。
  • 返回值

    • 如果转换成功,返回非零值(通常为1)。
    • 如果转换失败(例如,由于无效的IP地址格式),返回0。
4.3.2 inet_ntoa函数

  inet_ntoa 函数则是inet_aton的逆操作,它将网络字节序的二进制IP地址(存储在in_addr结构体中)转换回点分十进制的字符串形式。

  函数原型

#include <arpa/inet.h>

char *inet_ntoa(struct in_addr inaddr);
  • 参数

    • struct in_addr inaddr:包含网络字节序的二进制IP地址的in_addr结构体。
  • 返回值

    • 返回一个指向静态分配内存的字符串的指针,该字符串包含了点分十进制的IP地址。注意,这个字符串是静态分配的,意味着它在inet_ntoa的后续调用中可能会被覆盖。

4.4 listen函数介绍

4.4.1 listen函数原型
#include<sys/types.h>
#include<sys/socket.h>
int listen(int sockfd, int backlog);
4.4.2 listen函数功能
  • 设置能处理的最大连接数listen()函数并未开始接受连接,只是将套接字设置为监听模式。该函数仅用于服务器端。服务器进程不知道要与哪个客户端连接,因此它不会主动要求与某个进程连接,而是一直监听是否有其他客户进程与之连接,并响应该连接请求。一个服务进程可以同时处理多个客户进程的连接。
  • 主要功能有两个:
    1. 将一个未连接的套接字转换为一个被动套接字(监听状态)。
    2. 规定内核为相应套接字排队的最大连接数。
4.4.3 内核维护的队列
  • 未完成连接队列
    • 每个SYN报文段对应其中一项。这些报文段由客户端发出并到达服务器,而服务器正在等待完成相应的TCP三次握手过程。这些套接字处于SYN_RCVD状态。
  • 已完成连接队列
    • 每个已完成TCP三次握手过程的客户端对应其中一项。这些套接字处于ESTABLISHED状态。
4.4.4 listen函数参数
  • sockfd:socket系统调用返回的服务器端socket描述符。
  • backlog:指定在请求队列中允许的最大请求数。

4.5 accept函数介绍

4.5.1 accept函数原型
#include<sys/types.h>
#include<sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
4.5.1 accept函数功能

  accept函数由TCP服务器调用,用于从已完成连接队列队头返回下一个已完成连接。如果已完成连接队列为空,那么进程被投入睡眠。

4.5.2 accept函数参数
  • sockfd:socket系统调用返回的服务器端socket描述符。
  • addr:用来返回已连接的对端(客户端)的协议地址。
  • addrlen:客户端地址长度。
4.5.3 accept函数返回值
  • 该函数的返回值是一个新的套接字描述符,这个描述符表示已连接的套接字,而第一个参数sockfd是服务器监听套接字描述符。一个服务器通常仅创建一个监听套接字,它在该服务器的生命周期内一直存在。内核为每个由服务器进程接受的客户连接创建一个已连接套接字(表示TCP三次握手已完成)。当服务器完成对某个给定客户的服务时,相应的已连接套接字就会被关闭。

4.6 数据收发函数(一)

  在套接字通信中,进行字节读取的函数主要有read()write(),它们与标准I/O中的读取函数略有区别。这是因为在使用套接字时,实际输入或输出的字节数可能比请求的要少。

4.6.1 write()函数
  • 原型ssize_t write(int fd, const void *buf, size_t nbytes);
  • 说明:此函数将buf中的nbytes个字节写入到文件描述符fd中。成功时返回写入的字节数,出错则返回-1。
4.6.2 read()函数
  • 原型ssize_t read(int fd, void *buf, size_t nbyte);
  • 说明:此函数从文件描述符fd中读取nbyte个字节到buf中,返回实际读取的字节数。如果出错,则返回-1。
4.6.3 网络I/O 相关函数

  除了read()write()外,网络I/O还提供了其他函数,如:

  • recv() / send()
  • readv() / writev()
  • recvmsg() / sendmsg()
  • recvfrom() / sendto()

这些函数提供了更灵活和强大的网络通信能力。

4.6.4 recvfrom() 和 sendto()
  • recvfrom():用于从指定的套接字接收数据,并可以获取数据发送者的地址。
  • sendto():用于向指定的地址发送数据。

  这些函数特别适用于UDP等无连接的协议,因为它们允许直接指定数据的目标地址。

4.7 数据收发函数(二)

4.7.1 TCP套接字数据发送与接收函数

  发送数据函数

  在TCP套接字上发送数据的函数是 send()。该函数的使用包含以下三个关键要素:

  • 套接字s:表示已经建立连接的套接字描述符,即 accept() 函数的返回值。
  • 待发数据msg:指向存放待发送数据的缓冲区。
  • 数据长度len:待发送数据的长度。

函数原型:

ssize_t send(int s, const void *msg, size_t len, int flags);
  • 参数flags:控制选项,一般设置为0。

  接收数据函数

  在TCP套接字上接收数据的函数是 recv()。该函数同样包含三个关键要素:

  • 套接字s:面向连接的套接字描述符。
  • 接收缓冲区buf:用于存储接收到的数据。
  • 长度len:接收缓冲区的长度。

函数原型:

ssize_t recv(int s, void *buf, size_t len, int flags);
  • 参数flags:控制选项,一般设置为0。

4.8 connect函数介绍

  connect函数:客户机连接主机

4.8.1 connect函数原型
- `#include <sys/types.h>`
- `#include <sys/socket.h>`
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
4.8.2 connect函数功能
  • 该函数用于绑定之后的client端(客户端),与服务器建立连接。
4.8.3 connect函数参数
  • sockfd:是目的服务器的socket描述符。
  • addr:是服务器端的IP地址和端口号的地址结构指针。
  • addrlen:地址长度,常被设置为sizeof(struct sockaddr)
4.8.4 connect函数返回值
  • 成功时返回0,遇到错误时返回-1,并且errno中包含相应的错误码。

五 、scoket服务端代码实现一

5.1 字节序转换函数详细介绍

5.1.1htons() 和 htonl()
  • 功能:将主机字节序的值转换为网络字节序(大端字节序)。
  • 参数
    • uint16_t host16bitvaluehtons()):表示16位的主机字节序值,需要被转换为网络字节序。
    • uint32_t host32bitvaluehtonl()):表示32位的主机字节序值,同样需要被转换为网络字节序。
  • 返回值
    • htons() 返回一个uint16_t类型的值,即转换后的16位网络字节序值。
    • htonl() 返回一个uint32_t类型的值,即转换后的32位网络字节序值。
5.1.2 ntohs() 和 ntohl()
  • 功能:将网络字节序的值转换回主机字节序。
  • 参数
    • uint16_t net16bitvaluentohs()):表示从网络上接收到的16位网络字节序值,需要被转换回主机字节序。
    • uint32_t net32bitvaluentohl()):表示从网络上接收到的32位网络字节序值,同样需要被转换回主机字节序。
  • 返回值
    • ntohs() 返回一个uint16_t类型的值,即转换后的16位主机字节序值。
    • ntohl() 返回一个uint32_t类型的值,即转换后的32位主机字节序值。

这两个函数通常在网络通信的接收端使用,以确保接收到的数据能够被本地计算机正确理解和处理。

5.2 scoket服务端实现客户端连接程序

5.2.1 程序代码
#include <stdio.h>
使用socket函数需要包含以下头文件
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
//#include <linux/in.h>
#include <arpa/inet.h>
#include <netinet/in.h>

#include <stdlib.h>


int main()
{
	int s_fd = 0;				定义socket网络描述符
	struct sockaddr_in s_addr;	定义网络信息结构体变量
	int a_fd = 0;               定义accept连接描述符
	//1 socket
	s_fd = socket(AF_INET,SOCK_STREAM,0); 建立IPV4,TCP协议的服务器
	if(s_fd == -1)				判断是否建立成功
	{
		perror("socket");
		exit(-1);
	}
	//2.bind
	//int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
	s_addr.sin_family = AF_INET;		配置IPV4
	s_addr.sin_port   = htons(8989);	配置端口号
	inet_aton("192.168.208.132",&s_addr.sin_addr);	配置IP地址

	bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));	将服务器绑定信息
	//3.listen
	//int listen(int sockfd, int backlog);
	listen(s_fd,10);					将服务器设置为监听状态,监听10//4.accept
	//int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
	a_fd = accept(s_fd,NULL,NULL);	    将服务器设置为待连接状态,一但客户端连接就返回连接描述符
	//5.read

	//6.write


	printf("successful con\n");		连接成功后输出提示信息

	while(1);						防止程序退出


	return 0;
}
5.2.2 程序运行结果

  如下两图
在这里插入图片描述
在这里插入图片描述

六、 socket服务端代码实现二

6.1 socket服务端实现客户端连接收发数据程序

6.1.1 程序代码
#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
//#include <linux/in.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>

int main()
{
	int s_fd = 0;
	int a_fd = 0;
	int n_read = 0;
	struct sockaddr_in s_addr;
	struct sockaddr_in c_addr;		  定义客户端网络信息结构体变量
	char readBuf[128];
	
	memset(&s_addr,0,sizeof(struct sockaddr_in));
	memset(&c_addr,0,sizeof(struct sockaddr_in));	对客户端网络信息结构体变量清空
	//1 socket
	s_fd = socket(AF_INET,SOCK_STREAM,0);
	if(s_fd == -1)
	{
		perror("socket");
		exit(-1);
	}
	//2.bind
	//int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
	s_addr.sin_family = AF_INET;
	s_addr.sin_port   = htons(8989);
	inet_aton("192.168.208.132",&s_addr.sin_addr);

	bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));
	//3.listen
	//int listen(int sockfd, int backlog);
	listen(s_fd,10);
	//4.accept
	//int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
	int c_len = sizeof(struct sockaddr_in);			计算客户端网络信息结构体变量大小 
	a_fd = accept(s_fd,(struct sockaddr *)&c_addr,&c_len); 若连接客户端返回客户端IP地址和网络连接描述符
	if(a_fd == -1)	判断是否连接成功
	{
		perror("connet");      失败输出失败信息
	}
	else
	{
		printf("connet is : %s\n",inet_ntoa(c_addr.sin_addr));  成功输出客户端地址
	}

	//5.read
	n_read = read(a_fd,readBuf,128);		读取客户端发送的数据
	if(n_read == -1)			判断是否读取成功
	{
		perror("read");			失败输出失败原因
	}
	else
	{
		printf("n_read = %d, get data = %s\n",n_read,readBuf);  成功输出读取到的字节数和数据
	}
	//6.write
	write(a_fd,"I get your data",strlen("I get your data"));	向客户端发送信息
	
	while(1);					防止程序退出,以便用Windows CMD连接,不然会连接不上,或者连接上会出现发送数据后立即断开连接。

	return 0;
}
6.1.2 运行结果

  如下两图
在这里插入图片描述
在这里插入图片描述

七、 socket客户端代码实现

7.1 socket客户端连接服务器发送接收数据程序

7.1.1 程序代码
#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
//#include <linux/in.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>

int main()
{
	int c_fd = 0;
	int n_read = 0;
	struct sockaddr_in c_addr;
	char readBuf[128];
	
	memset(&c_addr,0,sizeof(struct sockaddr_in));
	//1 socket
	c_fd = socket(AF_INET,SOCK_STREAM,0);        获取socket网络描述符
	if(c_fd == -1)
	{
		perror("socket");
		exit(-1);
	}
	//2.connect									连接服务器
	c_addr.sin_family = AF_INET;				配置IPV4
	c_addr.sin_port   = htons(8989);			配置端口号
	inet_aton("192.168.208.132",&c_addr.sin_addr);	配置IP地址
	//int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
	if(connect(c_fd,(struct sockaddr *)&c_addr,sizeof(struct sockaddr_in)) == -1)							连接服务器并判断是否成功连接服务器
	{
		perror("connect");
		exit(-1);
	}
	//3.write								    向服务器发送数据
	write(c_fd,"I send the data",strlen("I send the data"));
	//4.read									接收服务器的回信
	n_read = read(c_fd,readBuf,128);
	if(n_read == -1)
	{
		perror("read");
	}
	else
	{
		printf("n_read = %d, get data = %s\n",n_read,readBuf);
	}

	
	while(1);									防止程序退出

	return 0;
}
7.1.2 程序运行结果

  如下图
在这里插入图片描述

八、 socket 客户端与服务器双方聊天

8.1 socket服务器实现多个客户端连接

8.1.1 客户端程序代码
#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
//#include <linux/in.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>

int main(int argc,char **argv)
{
	int c_fd = 0;
	int n_read = 0;
	struct sockaddr_in c_addr;
	char readBuf[128];
	
	memset(&c_addr,0,sizeof(struct sockaddr_in));
	//1 socket
	c_fd = socket(AF_INET,SOCK_STREAM,0);
	if(c_fd == -1)
	{
		perror("socket");
		exit(-1);
	}
	//2.connect
	c_addr.sin_family = AF_INET;
	c_addr.sin_port   = htons(atoi(argv[2]));    将端口号配置为默认参数三
	inet_aton(argv[1],&c_addr.sin_addr);		 将IP地址配置为默认参数二
	//int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
	if(connect(c_fd,(struct sockaddr *)&c_addr,sizeof(struct sockaddr_in)) == -1)
	{
		perror("connect");
		exit(-1);
	}
	//3.write
	write(c_fd,"I send the data",strlen("I send the data"));
	//4.read
	n_read = read(c_fd,readBuf,128);
	if(n_read == -1)
	{
		perror("read");
	}
	else
	{
		printf("n_read = %d, get data = %s\n",n_read,readBuf);
	}
	
	return 0;
}
8.1.2 服务器程序代码
#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
//#include <linux/in.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main(int argc,char **argv)
{
	int s_fd = 0;
	int a_fd = 0;
	int n_read = 0;
	int c_len = 0;
	struct sockaddr_in s_addr;
	struct sockaddr_in c_addr;
	char readBuf[128];
	
	memset(&s_addr,0,sizeof(struct sockaddr_in));
	memset(&c_addr,0,sizeof(struct sockaddr_in));
	//1 socket
	s_fd = socket(AF_INET,SOCK_STREAM,0);
	if(s_fd == -1)
	{
		perror("socket");
		exit(-1);
	}
	//2.bind
	//int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
	s_addr.sin_family = AF_INET;
	s_addr.sin_port   = htons(atoi(argv[2]));      将端口号配置为默认参数三
	inet_aton(argv[1],&s_addr.sin_addr);		   将IP地址配置为默认参数二

	bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));
	//3.listen
	//int listen(int sockfd, int backlog);
	listen(s_fd,10);
	//4.accept
	//int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
	while(1)		使用while(1)死循环,配置服务器一直允许连接状态
	{	
		c_len = sizeof(struct sockaddr_in); 
		a_fd = accept(s_fd,(struct sockaddr *)&c_addr,&c_len); 配置服务器允许连接
		if(a_fd == -1)
		{
			perror("connet");
		}
		else
		{
			printf("connet is : %s\n",inet_ntoa(c_addr.sin_addr));
		}

		if(fork()==0)			当有客户端连接服务器后,会新建一个子进程用于读取客户端传回的信息和回客户端信
		{
			//5.read
			n_read = read(a_fd,readBuf,128);	读取客户端发送的信息
			if(n_read == -1)
			{
				perror("read");
			}
			else
			{
				printf("n_read = %d, get data = %s\n",n_read,readBuf);	输出客户端发送的信息
			}
			//6.write
			write(a_fd,"I get your data",strlen("I get your data"));   给客户端回信
		}
	}

	return 0;
}
8.1.3 客户端服务器配合运行结果

  如下图
在这里插入图片描述

8.2 socket 服务器和客户端实现双方聊天

8.2.1 客户端程序代码
#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
//#include <linux/in.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>

int main(int argc,char **argv)
{
	int c_fd = 0;
	int n_read = 0;
	struct sockaddr_in c_addr;
	char readBuf[128];
	char sendBuf[128];
	
	memset(&c_addr,0,sizeof(struct sockaddr_in));
	//1 socket
	c_fd = socket(AF_INET,SOCK_STREAM,0);
	if(c_fd == -1)
	{
		perror("socket");
		exit(-1);
	}
	//2.connect
	c_addr.sin_family = AF_INET;
	c_addr.sin_port   = htons(atoi(argv[2]));
	inet_aton(argv[1],&c_addr.sin_addr);
	//int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
	if(connect(c_fd,(struct sockaddr *)&c_addr,sizeof(struct sockaddr_in)) == -1)
	{
		perror("connect");
		exit(-1);
	}
	while(1)
	{
		if(fork()==0)		新建一个进程用于向服务器发送数据
		{
			while(1)
			{
				//3.write
				memset(sendBuf,'\0',sizeof(sendBuf));     每次发送数据前对发送数据缓冲区进行清零
				printf("input: ");
				gets(sendBuf);							  从键盘获取发送的数据
				write(c_fd,sendBuf,strlen(sendBuf));	  向服务器发送数据
			}
		}
		while(1)
		{
			//4.read
			memset(readBuf,'\0',sizeof(readBuf));		  每次接收服务器数据前对接收数据缓冲区进行清零
			n_read = read(c_fd,readBuf,128);			  接收服务器数据
			if(n_read == -1)
			{
				perror("read");
			}
			else
			{
				printf("n_read = %d, get data = %s\n",n_read,readBuf);  输出接收到的服务器数据
			}

		}	
	}
	

	return 0;
}
8.2.2 服务器程序代码
#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
//#include <linux/in.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main(int argc,char **argv)
{
	int s_fd = 0;
	int a_fd = 0;
	int n_read = 0;
	int c_len = 0;
	struct sockaddr_in s_addr;
	struct sockaddr_in c_addr;
	char readBuf[128];
	char sendBuf[128];
	
	memset(&s_addr,0,sizeof(struct sockaddr_in));
	memset(&c_addr,0,sizeof(struct sockaddr_in));
	//1 socket
	s_fd = socket(AF_INET,SOCK_STREAM,0);
	if(s_fd == -1)
	{
		perror("socket");
		exit(-1);
	}
	//2.bind
	//int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
	s_addr.sin_family = AF_INET;
	s_addr.sin_port   = htons(atoi(argv[2]));
	inet_aton(argv[1],&s_addr.sin_addr);

	bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));
	//3.listen
	//int listen(int sockfd, int backlog);
	listen(s_fd,10);
	//4.accept
	//int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
	while(1)
	{	
		c_len = sizeof(struct sockaddr_in); 
		a_fd = accept(s_fd,(struct sockaddr *)&c_addr,&c_len);     使用while(1)死循环,配置服务器一直允许连接状态
		if(a_fd == -1)
		{
			perror("connet");
		}
		else
		{
			printf("connet is : %s\n",inet_ntoa(c_addr.sin_addr));
		}

		if(fork()==0)			当有客户端连接服务器后,会新建一个子进程用于接收客户端发送的数据
		{
			//6.write
			if(fork() == 0)     再新建一个进程用于回客户端的消息
			{
				while(1)
				{
					memset(sendBuf,'\0',sizeof(sendBuf));   每次回复前清楚发送缓冲区的数据
					printf("intput: ");
					gets(sendBuf);						    从键盘获取回复数据
					write(a_fd,sendBuf,strlen(sendBuf));	向客户端发送数据
				}
			}
			while(1)
			{
				//5.read
				memset(readBuf,'\0',sizeof(readBuf));		每次接收数客户端数据前对接收数据缓冲区进行清除
				n_read = read(a_fd,readBuf,128);			接收客户端发送的信息
				if(n_read == -1)
				{
					perror("read");
				}
				else
				{
					printf("n_read = %d, get data = %s\n",n_read,readBuf);						输出接收到的数据
				}
			}
		}
	}

	return 0;
}
8.2.3 客户端服务器配合运行结果

  如下图
在这里插入图片描述

九、 socket 服务器连接多客户端通信

9.1 资源竞争简图

请添加图片描述

9.2 服务器自动回复解决资源竞争程序代码

#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
//#include <linux/in.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main(int argc,char **argv)
{
	int s_fd = 0;
	int a_fd = 0;
	int n_read = 0;
	int c_len = 0;
	int mark = 0;
	struct sockaddr_in s_addr;
	struct sockaddr_in c_addr;
	char readBuf[128];
	char sendBuf[128];
	
	memset(&s_addr,0,sizeof(struct sockaddr_in));
	memset(&c_addr,0,sizeof(struct sockaddr_in));
	//1 socket
	s_fd = socket(AF_INET,SOCK_STREAM,0);
	if(s_fd == -1)
	{
		perror("socket");
		exit(-1);
	}
	//2.bind
	//int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
	s_addr.sin_family = AF_INET;
	s_addr.sin_port   = htons(atoi(argv[2]));
	inet_aton(argv[1],&s_addr.sin_addr);

	bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));
	//3.listen
	//int listen(int sockfd, int backlog);
	listen(s_fd,10);
	//4.accept
	//int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
	while(1)
	{	
		c_len = sizeof(struct sockaddr_in); 
		a_fd = accept(s_fd,(struct sockaddr *)&c_addr,&c_len);
		if(a_fd == -1)
		{
			perror("connet");
		}
		else
		{
			printf("connet is : %s\n",inet_ntoa(c_addr.sin_addr));
		}
		mark++;            每一个新的客户端连接就会加一
		if(fork()==0)
		{
			//6.write
			if(fork() == 0)
			{
				while(1)
				{
					memset(sendBuf,'\0',sizeof(sendBuf));
					sprintf(sendBuf,"Successful receive Client %d data",mark);  自动回复的内容
					write(a_fd,sendBuf,strlen(sendBuf));  进行自动回复
					sleep(3);
				}
			}
			while(1)
			{
				//5.read
				memset(readBuf,'\0',sizeof(readBuf));
				n_read = read(a_fd,readBuf,128);
				if(n_read == -1)
				{
					perror("read");
				}
				else
				{
					printf("n_read = %d, get data = %s\n",n_read,readBuf);
				}
			}
		}
	}

	return 0;
}

9.3 客户端程序代码和1.2.1一致

9.4 多个客户端配合服务器运行结果

  如下图
在这里插入图片描述

结束语

  非常感谢您的耐心阅读!在您即将离开之际,如果您觉得内容有所收获或启发,不妨动动手指,点个赞再走,这将是对我莫大的鼓励和支持。衷心感谢您的点赞与关注,期待未来能继续为您带来更多有价值的内容!谢谢您!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值