栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 系统运维 > 运维 > Linux

Linux网络编程简单服务端和客户端的实现

Linux 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Linux网络编程简单服务端和客户端的实现

一、socket套接字

        在网络编程中,socket是一个网络编程接口,是一种特殊的文件描述符,用来实现不同主机上的应用程序之间的通信。

二、套接字分类

        1.流套接字(SOCK_STREAM)

                流套接字用于提供面向连接、可靠的数据传输服务。该服务将保证数据能够实现无差错、无重复送,并按顺序接收。流套接字之所以能够实现可靠的数据服务,原因在于其使用了TCP协议。

        2.数据报套接字(SOCK_DGRAM)

                数据报套接字提供一种无连接的服务。该服务并不能保证数据传输的可靠性,数据有可能在传输过程中丢失或出现数据重复,且无法保证顺序地接收到数据。数据报套接字使用UDP协议进行数据的传输。由于数据报套接字不能保证数据传输的可靠性,对于有可能出现的数据丢失情况,需要在程序中做相应的处理。

        3.原始套接字(SOCK_RAW)

                原始套接字与标准套接字(标准套接字指的是前面介绍的流套接字和数据报套接字)的区别在于:原始套接字可以读写内核没有处理的IP数据包,而流套接字只能读取TCP协议的数据,数据报套接字只能读取UDP协议的数据。因此,如果要访问其他协议发送的数据必须使用原始套接。

三、API接口函数详解

        1.socket函数

        功能:创建一个新的socket,向系统申请一个socket资源。

        原型:int socket(int domain, int type, int protocol);

        参数:

                domain:指定协议域,决定socket的地址类型,如一般使用的AF_INET决定了使用ipv4地址(32位)与端口号(16位)的组合。

                type:socket类型,SOCK_STREAM(流套接字)、SOCK_DGRAM(数据报套接字)。

                protocol:指定协议,一般填0。

        返回值:成功返回socket套接字,否则返回-1,errno被设置。

        2.bind函数

        功能:绑定通信的地址和端口到socket上。

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

        参数:

                sockfd:创建好的socket套接字。

                addr:保存网络地址结构体的指针。

                addrlen:指定第二个实参指向的结构体的长度。

        返回值:成功返回0,否则返回-1,同时errno被设置。

        网络地址结构体:

        第一种:struct sockaddr {
                    sa_family_t    sin_family;          //协议族
                    char    sa_data[14];                  //包含了套接字中的目标地址和端口信息
                };

        第二种:struct sockaddr_in {
                sa_family_t    sin_family;         //协议族
                in_port_t sin_port;                  //端口号
                struct in_addr sin_addr;         //ip地址

                unsigned char sin_zero[8];//填充作用,无实际意义
            };
       因为第二种的端口号和IP地址是分开的,所以使用第二种方式保存IP地址和端口号,然后强转为第一种方式。

        3.listen函数

        功能:把主动连接的socket变为被动连接的socket,使socket可以接受其它socket的连接请求。(系统默认socket为主动连接)

        原型:int listen(int sockfd, int backlog);

        参数:

                socket:绑定好的socket套接字。

                backlog:同时能够处理连接请求的数目。

        返回值:成功返回0,否则返回-1,同时errno被设置。

        成功后,套接字会变成“监听套接字”;

        内核会为listen状态下的socket维护两个队列:

        1. 不完全连接请求队列(SYN_RECV状态);

        2. 等待accept建立socket的队列(ESTABLISHED状态)。

        4.accept函数

        功能:如果listen监听到客户端链接上来,则将客户端加入到监听的队列里,accept会从监听的队列里获取一个请求,如果监听到客户端连接的队列为空,accpet阻塞。
        原型:int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

        参数:

                sockfd:监听状态下的套接字。

                addr:保存网络地址结构体的指针,用于保存客户端的信息。

                addrlen:指定第二个实参指向的结构体的长度。

        返回值:成功返回与该客户端的连接套接字的描述符,后续与该客户端的数据的交换都是通过这个连接套接字才能进行;失败返回-1,同时errno被设置。

        

        5.connet函数

        功能:向服务端发送请求。

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

        参数:           

                sockfd:连接套接字的描述符。
                addr:保存要连接的服务端地址。
                addrlen:第二个参数指向的结构体的长度。

        返回值:成功返回0,否则返回-1,同时errno被设置。

        

        6.send函数和sendto函数

        功能:发送数据

        原型:ssize_t send(int sockfd, const void *buf, size_t len, int flags);

                   ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);

        参数:

                sockfd:服务端的是accept返回的套接字,客户端就是创建的套接字。

                buf:保存需要发送的数据内存地址。

                len:需要发送的数据的长度。

                flags:一般填0。

                addr:网络地址结构体的指针。

                addrlen:网络地址结构体的长度的指针。

        返回值:成功返回实际发送出去的字节数,失败返回-1,同时errno被设置。

        

        7.recv函数和recvfrom函数

        功能:接收数据。

        原型:ssize_t recv(int sockfd, void *buf, size_t len, int flags);

                   ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);

        参数:

                sockfd:服务端的是accept返回的套接字,客户端就是创建的套接字。

                buf:保存收到数据的内存地址。

                len:需要接收的数据的长度。

                flags:一般填0。

                addr:网络地址结构体的指针。

                addrlen:网络地址结构体的长度的指针。

四、服务端和客户端实现流程图

        1.基本TCP客户/服务器程序的套接字函数  :      

          2.基本UDP客户/服务器程序的套接字函数:

 

五、数据在网络上传输的问题

        1.网络字节序的问题

                网络上传输的数据都是字节流,在多字节数值发送之前,数据应该要以大端模式存放,因为TCP/UDP/IP协议规定:把接收到的第一个字节当做高位字节看待,要求发送方发送的第一个字节必须得是高位。

                整数在主机字节序与网络字节序之间的转换函数:

            uint32_t htonl(uint32_t hostlong);   //把一个32bits的整数的主机字节序转换成网络字节序
            uint16_t htons(uint16_t hostshort); //把一个16bits的整数的主机字节序转换成网络字节序
            uint32_t ntohl(uint32_t netlong);     //把一个32bits的整数的网络字节序转换成主机字节序                uint16_t ntohs(uint16_t netshort);   //把一个16bits的整数的网络字节序转换成主机字节序

        2.IP地址转换问题

                IP地址是以点分十进制的形式存在的,在网络传输中,要把点分十进制的形式转换为IP的网络地址。

                IP地址转换函数:

                int inet_aton(const char *cp, struct in_addr *inp);
                cp:指向要转换的点分十进制字符串
                inp:指向一个IP的地址结构体struct in_addr用于保存转换后的32bit的IP地址的.
                返回值:成功返回0失败返回-1,同时errno被设置
          
                in_addr_t inet_addr(const char *cp); //把cp指向的点分十进制字符串转换成IP的网络地址in_addr_t(uint32_t)。

                in_addr_t inet_network(const char *cp);  // 与inet_addr类似
            
               char *inet_ntoa(struct in_addr in); // 把32bit存在形式的网络地址变成点分十进制的字符串。

六、基于TCP的服务端和客户端代码

        1.TCP服务端代码如下:

#include "head.h"

//创建一个服务端套接字
//参数:IP地址 和 端口号
//返回值:成功返回0,否则返回-1
int create_socket(char * ip,int port)
{
	//1.创建套接字
	int sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if(-1 == sockfd)
	{
		perror("Create socket error:");
		return -1;
	}

	//设置套接字
	int on = 1;
	setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (void *)&on, sizeof(on));
	setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT, (void *)&on, sizeof(on));
	
	//初始化ip/port
	struct sockaddr_in server;	
	server.sin_family = AF_INET;
	server.sin_port = htons(port);
	server.sin_addr.s_addr=inet_addr(ip);
	//绑定IP
	int ret = bind(sockfd, (struct sockaddr *)&server,sizeof(server));
	if(-1 == ret)
	{
		perror("Bind error:");
		close(sockfd);
		return -1;
	}

	//进入监听模式
	ret = listen(sockfd, 10);
	if(-1 == ret)
	{
		perror("Join listen error:");
		close(sockfd);
		return -1;
	}

	return sockfd;
}

//与客户端进行通讯
void Handle_Connect(int confd,struct sockaddr_in client)
{
	int ret;
	while(1)
	{
		char buf[256] = {0};
		ret = recv(confd,buf,sizeof(buf),0);
		if(ret > 0)
		{
			printf("Recv Data [%s] From [IP:%s,PORT:%d]n",buf,inet_ntoa(client.sin_addr),ntohs(client.sin_port));

			char resp[256] = {"I recv your data!"};
			//回消息给客户端
			send(confd,resp,strlen(resp),0);
		}
	}
}

int main(int argc,char **argv)
{
	if(argc != 3)
	{
		fprintf(stderr,"USAGE: %s  n",argv[0]);
		return -1;
	}
	
	//1.创建已进入监听模式的服务端套接字
	int sockfd = create_socket(argv[1],atoi(argv[2]));

	//2.建立连接
	struct sockaddr_in client;
	socklen_t len = sizeof(client);
	//建立连接,返回连接套接字
	int confd = accept(sockfd, (struct sockaddr *)&client, &len);
	if(-1 == confd)
	{
		perror("Connect error:");
	}
	
	while(1)
	{	
		pid_t pid = fork();
		if(-1 == pid)
		{
			perror("Fork error:");
			continue;
		}
		else if(0 == pid)
		{
			//与客户端的数据传递
			Handle_Connect(confd,client);
		}
		else
		{
			close(confd);
		}
	}

	close(sockfd);
	return 0;
}

        2.TCP客户端代码如下:

#include "head.h"

//创建一个客户端套接字
//参数:IP地址 和 端口号
//返回值:成功返回0,否则返回-1
int create_socket(char * ip,int port)
{
	//1.创建套接字
	int confd = socket(AF_INET, SOCK_STREAM, 0);
	if(-1 == confd)
	{
		perror("Create socket error:");
		return -1;
	}

	return confd;
}

int main(int argc,char **argv)
{
	if(argc != 3)
	{
		fprintf(stderr,"USAGE: %s  n",argv[0]);
		return -1;
	}
	
	//1.创建套接字
	int confd = create_socket(argv[1],atoi(argv[2]));

	//2.建立连接
	struct sockaddr_in server;
	server.sin_family = AF_INET;					//指定协议族
	server.sin_port  = htons(atoi(argv[2]));		//指定网络程序的端口号
	server.sin_addr.s_addr = inet_addr(argv[1]);	//指定IP地址
	int ret = connect(confd,(struct sockaddr *)&server,sizeof(server));
	if(ret == -1)
	{
		perror("Connect Server Failed");
		return -1;
	}

	char buf[256];
	while(1)
	{
		puts("请输入要传输的数据:");
		//通过套接字往服务器上发送数据
		scanf("%s",buf);
		send(confd,buf,strlen(buf),0);

		//接收服务器回给我们的数据
		recv(confd,buf,sizeof(buf),0);
		printf("服务器回应:%sn",buf);
	}

	close(confd);

	return 0;
}


七、基于UDP服务端和客户端代码

        1.UDP服务端代码如下:

#include "head.h"

//创建绑定好的套接字
//成功返回套接字,否则返回-1
int create_socket(char * ip, int port)
{
	//创建套接字
	int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
	if(-1 == sockfd)
	{
		perror("Socket creatr error:");
		return -1;
	}

	//网络地址结构体
	struct sockaddr_in server;
	server.sin_family = AF_INET;
	server.sin_port = htons(port);
	server.sin_addr.s_addr=inet_addr(ip);
	//绑定
	int ret = bind(sockfd, (struct sockaddr *)&server,sizeof(server));
	if(-1 == ret)
	{
		perror("Bind error:");
		close(sockfd);
		return -1;
	}

	return sockfd;
}

int main(int argc,char **argv)
{
	if(argc != 3)
	{
		fprintf(stderr,"%s  n",argv[0]);

		return -1;
	}
	//1.创建绑定好的套接字
	int sockfd = create_socket(argv[1],atoi(argv[2]));

	struct sockaddr_in client;
	int len = sizeof(client);
	//2.接收和发送数据
	while(1)
	{
		char buf[256] = {0};
		int ret = recvfrom(sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&client, &len);
		if(ret > 0)
		{
			printf("Data<%s> from IP<%s> PORT<%d> clientn",buf,inet_ntoa(client.sin_addr),ntohs(client.sin_port));
		}
		if(ret < 0)
		{
			perror("Recv false:");
			continue;
		}
	}
	close(sockfd);

	return 0;
}

        2.UDP客户端代码如下:

#include "head.h"

//创建绑定好的套接字
//成功返回套接字,否则返回-1
int create_socket(char * ip, int port)
{
	//创建套接字
	int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
	if(-1 == sockfd)
	{
		perror("Socket creatr error:");
		return -1;
	}

	return sockfd;
}

int main(int argc,char **argv)
{
	if(argc != 3)
	{
		fprintf(stderr,"%s  n",argv[0]);

		return -1;
	}
	//1.创建套接字
	int sockfd = create_socket(argv[1],atoi(argv[2]));

	struct sockaddr_in cliten;
	int len = sizeof(cliten);
	cliten.sin_family = AF_INET;					//指定协议族
	cliten.sin_port  = htons(atoi(argv[2]));		//指定网络程序的端口号
	cliten.sin_addr.s_addr = inet_addr(argv[1]);	//指定IP地址
	//2.接收和发送数据
	while(1)
	{
		char buf[256] = {0};
		printf("请输入要发送的数据:n");
		scanf("%s",buf);
		int ret = sendto(sockfd, buf, sizeof(buf) + 1, 0, (struct sockaddr *)&cliten, len);
		if(-1 == ret)
		{
			perror("Sendto error:");
			continue;
		}
	}
	close(sockfd);

	return 0;
}

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/288743.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号