Linux —— 网络编程

目录

一. 为什么要进行网络编程?

二. 网络编程的特点

三. 套接字(Socket)

3.1 常用的通讯协议

3.1.1 TCP协议

3.1.2 UDP协议

3.1.3 TCP与UDP的比较区别

3.1.4 字节序

四. Socket的服务器与客户端建立

4.1 网络编程的相关函数

4.1.1 socket函数

4.1.2 bind 函数

4.1.3 listen函数

4.1.4 accept函数

4.1.5 connect函数

五. 示例代码

5.1 服务端 与 客户端的建立

 5.2 实现双机聊天


一. 为什么要进行网络编程?

跨越计算机:

        网络编程能够实现不同计算机之间的通信,而IPC主要用于同一台计算机上的进程之间通信。因此,网络编程可以应用于分布式系统、互联网等需要跨越不同计算机的场景。

扩展性:

        网络编程支持多个客户端与一个服务器进行通信,可以实现服务端集中管理、多客户端同时连接的情况。这样可以更好地满足大规模并发访问的需求,提高系统的扩展性。

跨平台:

        网络编程基于网络协议如TCP/IP,是一种标准化的跨平台通信方式。不同操作系统和编程语言都支持网络编程,因此可以实现不同平台之间的互联互通。

网络资源利用

        通过网络编程,可以利用互联网上的各种资源,如数据库、云存储、Web服务等。这样可以实现更丰富的功能和服务,增加系统的灵活性和功能性

二. 网络编程的特点

分布式通信:

        网络编程可以实现分布式系统中不同计算机之间的通信。通过网络协议,可以将任务和计算资源分布在不同的计算机上,实现协同工作和资源共享

客户端-服务器模型

        网络编程通常采用客户端-服务器模型,其中服务器提供服务,而客户端请求并接收服务。服务器等待客户端的连接请求,并对其做出响应。

基于套接字

        网络编程使用套接字(Socket)作为通信的接口和机制。套接字提供了一组接口和方法,使得应用程序能够通过网络进行数据传输和通信

网络协议

        网络编程依赖于各种网络协议,如TCP/IP协议栈。网络协议规定了数据封装和传输的方式,保证了数据的可靠传输、拥塞控制等功能

并发和多线程

        网络编程需要处理同时存在多个客户端请求的情况。为了提高并发性能,通常使用多线程或多进程来处理多个客户端请求,实现并发处理和响应

异步通信

        网络编程中常常涉及异步通信,即发送方和接收方不需要同步进行操作。异步通信可以提高系统的效率和响应速度

三. 套接字(Socket)

        套接字(Socket)是网络编程中用于实现网络通信的一种机制或接口。它提供了一组函数和方法,使得应用程序能够通过网络进行数据传输和通信。

        套接字通过网络协议(如TCP/IP)在计算机之间建立连接,并在连接上进行数据的发送和接收。它可以被看作是网络中两个应用程序之间的端点,一个套接字位于发送方,另一个套接字位于接收方。

        在TCP/IP协议中,一个进程通过IP地址和TCP或UDP端口号来标识自己,同一台计算机上不同的进程可以使用相同的IP地址但不同的端口号。当一个进程需要与另一个进程通信时,它需要创建一个Socket套接字来表示自己,并使用目标进程的IP地址和端口号来连接到对应的Socket套接字。这样,两个Socket套接字就组成了一个Socket pair来唯一标识这个连接。在网络编程中,我们可以使用Socket API来创建、配置、连接、发送和接收数据以及关闭Socket套接字等操作。

3.1 常用的通讯协议

3.1.1 TCP协议

        TCP,即传输控制协议(Transmission Control Protocol),是一种网络传输协议,可以让我们的计算机和其他设备通过互联网彼此交流。      

        想象一下,你要给一个朋友发一封电子邮件。你将这封邮件分成小块,在每个小块上都写上编号。然后,你把这些小块一个一个地寄给你的朋友。你的朋友在收到邮件的小块后,按照编号的顺序将它们重新组装在一起,最终得到完整的邮件

        TCP就像是这样一个邮件系统。当你发送数据时,TCP会将数据分成一小块一小块的报文段,并为每个报文段添加编号。然后,TCP会确保这些报文段按照正确的顺序到达接收方,并在接收方重新组装这些报文段,以确保数据完整无误

        不仅如此,TCP还能确保数据的可靠传输。如果有一个报文段在传输过程中丢失了,TCP会负责重新发送这个报文段,以确保接收方可以正确地收到数据。而且,TCP还可以调整传输速度,以适应网络的拥塞情况,确保数据能够流畅地传输

        所以,TCP就像是一个可靠的邮递员,负责将你发送的数据安全、完整地交付给目标设备,让你可以在互联网上进行可靠的通信

3.1.2 UDP协议

        UDP,即用户数据报协议(User Datagram Protocol),是一种简单的网络传输协议,与TCP相比更加轻量级和灵活。

        想象一下,你要给一个朋友发一张明信片。你写下了你要说的话,然后在明信片上写上你朋友的地址,最后把它扔进邮箱寄出去你并不关心明信片是否被正确送达,也不需要确认你的朋友是否已经收到

        UDP就像这样一个明信片系统。当你发送数据时,UDP会将你的数据打包成一个个数据报,然后直接发送给接收方它不会像TCP那样进行可靠的数据重传和顺序调整,也不会提供连接管理的功能。

        UDP之所以被称为轻量级和灵活,是因为它不需要建立连接,也没有复杂的数据校验机制。它只是简单地将你的数据发送出去,尽可能快地到达目标设备,并让接收方自行处理接收到的数据。

        因为UDP没有像TCP那样的可靠性保证,所以它适用于一些对实时性要求较高、可以容忍少量数据丢失的应用场景。例如,音频和视频流的传输、在线游戏的实时交互等。在这些情况下,速度和实时性更为重要,而数据的准确性可以稍微牺牲一些。

        所以,UDP就像是一张速递的明信片,快速地将数据发送给目标设备,虽然不保证到达和完整性,但在某些特定的应用场景中非常实用。

3.1.3 TCP与UDP的比较区别

  1. 可靠性TCP提供可靠的数据传输,确保数据按照正确的顺序到达目标设备,并能够进行丢失的重传。而UDP则是一种不可靠的传输协议,它不提供数据重传和顺序调整的机制

  2. 连接性TCP是面向连接的协议,它在通信双方之间建立了一个稳定的连接,然后才进行数据的传输UDP则是无连接的协议,它不需要事先建立连接,可以直接将数据报发送给接收方

  3. 效率和延迟由于TCP提供了可靠性保证和连接管理机制,它在数据传输过程中会产生较多的额外开销,例如建立连接、数据校验等。这使得TCP相对于UDP来说,在效率和传输延迟方面略低一些

  4. 适用场景TCP适用于对数据传输的准确性和完整性要求较高的场景,例如文件传输、网页浏览等。而UDP则适用于对实时性要求较高、可以容忍少量数据丢失的场景,例如音频/视频流的传输、实时游戏等

  5. 数据量限制由于TCP面向连接且提供可靠性保证,它对数据大小的限制比较松散,可以适应大量的数据传输。而UDP在发送数据时有一个固定的最大数据报大小限制,超过这个限制则需要进行分片处理

3.1.4 字节序

        字节序(Byte Order)指在计算机存储和表示多字节数据时,字节的顺序排列方式。常见的字节序有两种:大端序(Big Endian)小端序(Little Endian)

  1. 大端序(Big Endian)数据的高位字节存储在低地址,低位字节存储在高地址。

  2. 小端序(Little Endian):数据的低位字节存储在低地址,高位字节存储在高地址。

        不同的计算机体系结构和处理器可能采用不同的字节序。对于网络通信来说,字节序的统一非常重要,以确保数据能够正确地被接收和解析。因此,在网络传输中,经常需要进行字节序的转换。

为了实现字节序的转换,可以使用以下函数

#include <arpa/inet.h>
 
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);

//h表示host,n表示network,l表示32位长整数,s表示16位短整数。
//如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回,
//如果主机是大端字节序,这些函数不做转换,将参数原封不动地返回。

四. Socket的服务器与客户端建立

4.1 网络编程的相关函数

4.1.1 socket函数

作用创建套接字,用于通信
头文件

#include <sys/types.h>

#include <sys/socket.h>

函数int socket(int domain, int type, int protocol);
参数

domain(指定通信域):

        AF_INET                                 IPV4使用

        AF_INET6                               IPV6使用

        AF_UNIX 或 AF_LOCAL         本地通信使用

        AF_PACKET                            原始套接字使用

type(套接字类型):

        SOCK_STREAM                     TCP使用

        SOCK_DGRAM                      UDP使用

        SOCK_RAW                           原始套接字使用

protocol:

        附加协议 如果没有附加协议 传 0 即可

返回值

成功:返回文件描述符

失败:返回-1

4.1.2 bind 函数

作用将套接字和网络信息结构体绑定
头文件

#include <sys/types.h>

#include <sys/socket.h>

函数

int bind(int sockfd,const struct sockaddr *addr,socklen_t addrlen);

参数

sockfd :

        socket函数返回的套接字

addr:

        struct sockaddr_in

        {

                sa_family_t sin_family;         /* AF_INET */

                in_port_t sin_port;                /* 网络字节序的端口号 */

                struct in_addr sin_addr;       /* IP地址 */

         };

addrlen:

        addr的大小

返回值

成功:返回 0

失败:返回-1

4.1.3 listen函数

作用将套接字设置成被动监听状态
头文件

#include <sys/types.h>

#include <sys/socket.h>

函数int listen(int sockfd, int backlog);
参数

sockfd:

        socket函数返回的套接字

backlog:

        允许同时连接服务的客户端的个数(只要不是0就可以)

返回值

成功:返回 0

失败:返回-1

4.1.4 accept函数

作用

阻塞等待客户端连接,一旦有客户端连接,就会返回一个

新的套接字(文件描述符),专门用于和该客户端进行通信

头文件

#include <sys/socket.h>

函数int accept(int sockfd,struct sockaddr *addr,socklen_t *addrlen);
参数

sockfd:

        socket函数返回的套接字

addr:

        用来保存客户端网络信息结构体的首地址,

        如果不关心客户端的信息,可以传NULL

addrlen:

        addr长度,如果没有可以传NULL

返回值

成功:返回文件描述符 专门用于和该客户端通信

失败:返回-1

4.1.5 connect函数

作用与服务器建立连接
头文件

#include <sys/types.h>

#include <sys/socket.h>

函数

int connect(int sockfd,const struct sockaddr *addr,socklen_t addrlen);

参数

sockfd:

        socket函数返回的套接字

addr:

        要连接的服务器的网络信息结构体

addrlen:

        addr长度

返回值

成功:返回 0

失败:返回-1

五. 示例代码

5.1 服务端 与 客户端的建立

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

int main()
{
	/*认识服务端的创建*/
	struct sockaddr_in serveraddr;
	struct sockaddr_in clineaddr;

	memset(&serveraddr,0,sizeof(struct sockaddr_in));
	memset(&clineaddr,0,sizeof(struct sockaddr_in));
/*---------------------------------------------------------------------------------*/
	//1.创建一个套接字
	int serverfd;				  //返回的网络描述符
	serverfd = socket(AF_INET,SOCK_STREAM,0);//现在配的是IPv4,TCP协议 
	if(serverfd == -1)
	{
		perror("socket");
		exit(-1);
	}
/*---------------------------------------------------------------------------------*/
	//2.根据网络描述符绑定IP和端口
	//用另外一种结构体
	serveraddr.sin_family = AF_INET; //地址族,现在是IPV4
	
	//端口号,htons是将整型变量从主机字节顺序转变成网络字节顺序
	serveraddr.sin_port = htons(8888);
	
	//将一个字符串IP地址转换为一个32位的网络序列IP地址
	inet_aton("192.168.128.132",&serveraddr.sin_addr);
	
		   //强制转换成sockaddr
	bind(serverfd,(struct sockaddr*)&serveraddr,sizeof(struct sockaddr_in));
/*---------------------------------------------------------------------------------*/
	//3.监听
	listen(serverfd,10);//现在配的是最大连接个数为10
/*---------------------------------------------------------------------------------*/
	//4.接收
	int len = sizeof(struct sockaddr_in);
	int acceptfd = accept(serverfd,(struct sockaddr*)&clineaddr,&len);
	if(acceptfd == -1)
	{
		perror("accept");
	}
	else
	{
		printf("connect ip is %s\n",inet_ntoa(clineaddr.sin_addr));
	}
/*---------------------------------------------------------------------------------*/
	//5.接收read
	char buf[128] = {0};
	int n_read = read(acceptfd,buf,128);
	if(n_read == -1)
	{
		perror("read");
	}
	else
	{
		printf("server recv the data[%d] is %s\n",n_read,buf);
	}
/*---------------------------------------------------------------------------------*/
	//6.发送write
	char *str = "I'm server";
	write(acceptfd,str,strlen(str));
/*---------------------------------------------------------------------------------*/

	printf("connect\r\n");
	return 0;
}

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

int main()
{
	/*认识客户端的创建*/
	struct sockaddr_in clineaddr;
	memset(&clineaddr,0,sizeof(struct sockaddr_in));
/*---------------------------------------------------------------------------------*/
	//1.创建一个套接字
	int clinefd;//返回的网络描述符
	clinefd = socket(AF_INET,SOCK_STREAM,0);//现在配的是IPv4,TCP协议 
	if(clinefd == -1)
	{
		perror("socket");
		exit(-1);
	}
/*---------------------------------------------------------------------------------*/
	//2.连接
	//用另外一种结构体
	clineaddr.sin_family = AF_INET; //地址族,现在是IPV4
	//端口号,htons是将整型变量从主机字节顺序转变成网络字节顺序
	clineaddr.sin_port = htons(8888);
	//将一个字符串IP地址转换为一个32位的网络序列IP地址
	inet_aton("192.168.128.132",&clineaddr.sin_addr);

		     //强制转换成sockaddr
	if(connect(clinefd,(struct sockaddr *)&clineaddr,sizeof(struct sockaddr)) == -1)
	{
		perror("connect");
		exit(-1);
	}
/*---------------------------------------------------------------------------------*/
	//3.发送write
	char *str = "I'm cline!";
	write(clinefd,str,strlen(str));
/*---------------------------------------------------------------------------------*/
	//4.接收read
	char buf[128] = {0};
	int n_read = read(clinefd,buf,128);
	if(n_read == -1)
	{
		perror("read");
	}
	else
	{
		printf("cline:recv the data[%d] is  %s\n",n_read,buf);
	}
/*---------------------------------------------------------------------------------*/
	return 0;
}

运行结果:

 5.2 实现双机聊天

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

int main(int argc,char **argv)
{

	/*实现双机聊天*/
	struct sockaddr_in serveraddr;
	struct sockaddr_in clineaddr;

	memset(&serveraddr,0,sizeof(struct sockaddr_in));
	memset(&clineaddr,0,sizeof(struct sockaddr_in));
/*--------------------------------------------------------------------------*/
	//1.创建一个套接字
	int serverfd;//返回的网络描述符
	serverfd = socket(AF_INET,SOCK_STREAM,0);//现在配的是IPv4,TCP协议 
	if(serverfd == -1)
	{
		perror("socket");
		exit(-1);
	}
/*--------------------------------------------------------------------------*/
	//2.根据网络描述符绑定IP和端口
	//用另外一种结构体
	serveraddr.sin_family = AF_INET; //地址族,现在是IPV4

	//端口号,htons是将整型变量从主机字节顺序转变成网络字节顺序
	serveraddr.sin_port = htons(atoi(argv[2]));

	//将一个字符串IP地址转换为一个32位的网络序列IP地址
	inet_aton(argv[1],&serveraddr.sin_addr);
	       //强制转换成sockaddr
	bind(serverfd,(struct sockaddr*)&serveraddr,sizeof(struct sockaddr_in));
/*--------------------------------------------------------------------------*/
	//3.监听
	listen(serverfd,10);//现在配的是最大连接个数为10
/*--------------------------------------------------------------------------*/
	//4.接收
	int acceptfd;
	int n_read;
	char str[128] = {0};
	char buf[128] = {0};
	int len = sizeof(struct sockaddr_in);

	while(1)
	{
		//每次都接收新的客户端
		acceptfd = accept(serverfd,(struct sockaddr*)&clineaddr,&len);
		if(acceptfd == -1)
		{
			perror("accept");
		}
		else
		{
			printf("connect ip is %s\n",inet_ntoa(clineaddr.sin_addr));
		}

		//创建子进程用于接收
		if(fork() == 0)
		{
			while(1)
			{
				memset(buf,0,sizeof(buf));
				n_read = read(acceptfd,buf,128);
				if(n_read == -1)
				{
					perror("read");
				}
				else
				{
					printf("cline: %s\n",buf);
				}
			}
		}

		//用于发送功能
		while(1)
		{	
			memset(str,0,sizeof(str));
			fgets(str,sizeof(str),stdin);		
			write(acceptfd,str,strlen(str));
		}	
	}
}
#include <stdio.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <sys/types.h> 
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>

int main(int argc,char **argv)
{
	/*实现双机的聊天*/
	struct sockaddr_in clineaddr;
	memset(&clineaddr,0,sizeof(struct sockaddr_in));
/*--------------------------------------------------------------------------*/
	//1.创建一个套接字
	int clinefd;//返回的网络描述符
	clinefd = socket(AF_INET,SOCK_STREAM,0);//现在配的是IPv4,TCP协议 
	if(clinefd == -1)
	{
		perror("socket");
		exit(-1);
	}
/*--------------------------------------------------------------------------*/
	//2.连接
	//用另外一种结构体
	clineaddr.sin_family = AF_INET; //地址族,现在是IPV4
	//端口号,htons是将整型变量从主机字节顺序转变成网络字节顺序
	clineaddr.sin_port = htons(atoi(argv[2]));
	//将一个字符串IP地址转换为一个32位的网络序列IP地址
	inet_aton(argv[1],&clineaddr.sin_addr);

		        //强制转换成sockaddr
	if(connect(clinefd,(struct sockaddr *)&clineaddr,sizeof(struct sockaddr)) == -1)
	{
		perror("connect");
		exit(-1);
	}
/*--------------------------------------------------------------------------*/
	int n_read;
	char str[128] = {0};
	char buf[128] = {0};
				
	while(1)
	{
		//创建子进程用于接收功能
		if(fork() == 0)
		{
			while(1)
			{
				memset(buf,0,sizeof(buf));
				n_read = read(clinefd,buf,128);
				if(n_read == -1)
				{
					perror("read");
				}
				else
				{
					printf("server: %s\n",buf);
				}
			}
		}
		//用于发送功能
		while(1)
		{			
			memset(str,0,sizeof(str));
			fgets(str,sizeof(str),stdin);
			write(clinefd,str,strlen(str));			
		}
	}
}

运行结果:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值