Linux——网络socket(server、client)

Linux——网络socket(server、client)

前言:
对于APUE网络socket,我们需要了解就是server和client之间的通信建立过程。首先我们需要知道两台不同网段的pc是怎么通信的,当一台pc通过应用程序发一段消息过来给另一台pc时,pc获取到数据包之后就行拆包解析,这些都是操作系统内核的工作,对于server而言,需要告诉内核使用什么ip和端口来建立起服务,当有client接入时,内核先进行解析,得到client的ip和访问端口之后再给server(对应的应用程序),最后完成通信建立;对于client而言,需要知道server的ip和端口,获取ip来源主要是通过dns解析和人为获取,然后通过获取的ip和端口进行发送连接请求,最后完成通信。

一、server

1、socket()建立好通讯协议
int socket(int domain, int type, int protocol);

(1)domain:包含了ipv4和ipv6的协议族,AF_INET(ipv4)AF_INET6(ipv6)、
AF_LOCA(或称AF_UNIX,Unix域socket)、AF_ROUTE等等

(2)type:指定socket类型。常用的socket类型有,SOCK_STREAM(TCP流)、SOCK_DGRAM(UDP流)、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等等。

(3)protocol:指定协议。常用的协议有IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC、它们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议,type和protocol并不是可以随意组合的,如SOCK_STREAM不可以跟IPPROTO_UDP组合。当protocol为0时,会自动选择type类型对应的默认协议。

socket_fd = socket(AF_INET, SOCK_STREAM, 0);内核的返回值为创建一个socket的描述符,socket_fd;
2、bind()提供服务器的ip和端口
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

sockfd:即socket描述字,它是通过socket()函数创建了,唯一标识一个socket。bind()函数就是将给这个描述字绑定一个名字。

addrlen:对应的是地址的长度。

addr:一个const struct sockaddr *指针,指向要绑定给sockfd的协议地址。这个地址结构根据地址创建socket时的地址协议族的不同而不同,但最终都会强制转换后赋值给sockaddr这种类型的指针传给内核。

bind返回值 < 0 则失败,>0 成功
这里涉及了几个结构体、通用套接字类型,ipv4类型和ipv6类型,不管是ipv4还是ipv6,在传数据给内核时都要使用通用型的,所以数据需要强行转换。

#define LISTEN_PORT 8889
struct sockaddr_in serveraddr;

serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(LISTEN_PORT); //操作系统的字节序为小端字序,所以需要转换成网络的大端字节序,端口为short型,用htons
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY); //ip地址为4个字节,用htonl;INADDR_ANY(实际上是0)可以自动选择服务器的ip,使用有线网卡就用有线ip,使用无线网卡就用无线ip。
bind(sock_fd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)); //不管是ipv4还是ipv6,在传数据给内核时都要使用通用型的,所以数据需要强行转换。
通用型
typedef unsigned short int sa_family_t;
struct sockaddr {
sa_family_t sa_family; /* 2 bytes address family, AF_xxx */
char sa_data[14]; /* 14 bytes of protocol address */
}
ipv4
typedef unsigned short sa_family_t;
typedef uint16_t in_port_t;
struct in_addr {
uint32_t s_addr;
};
struct sockaddr_in {
sa_family_t sin_family; /* 2 bytes address family, AF_xxx such as AF_INET */
in_port_t sin_port; /* 2 bytes port*/
struct in_addr sin_addr; /* 4 bytes IPv4 address*/
/* Pad to size of `struct sockaddr'. */unsigned char sin_zero[8]; /* 8 bytes unused padding data, always set be zero */
};

3、listen()监听socket,等待客户端连接
这个用来监听socket,等待客户端连接;通过前面两个步骤,应用程序已经告诉内核要使用什么通信协议,用什么ip和端口来建立服务器,这个时候就等待有谁来进行访问就可以了,相当于打开了这个应用程序,比如QQ的聊天一样。

int listen(int sockfd, int backlog);

sockefd: socket()系统调用创建的要监听的socket描述字
backlog: 相应socket可以在内核里排队的最大连接个数,比如 13,正在连接服务器的最大数只能是13,超过就连不上。
#define BACKLOG 13
listen(listen_fd, BACKLOG);

4、accept()
接受客户端的连接请求,同时产生新的socket描述符,用于服务某个客户端,一对一的关系。

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
这里的意思是,当服务器接受到连接请求时,收到的数据包首先是经过操作系统处理好的,所以要想应用程序知道客户端的ip和端口,需要向内核拿数据,accept函数就是处理好这一部分工作。

sockfd: 服务器开始调用socket()函数生成的,称为监听socket描述字;
*addr: 用于返回客户端的协议地址,这个地址里包含有客户端的IP和端口信息等;
addrlen: 返回客户端协议地址的长度
返回值为一个新的socket文件描述符,这个用来与客户端之间相互通信。

int	client_fd =-1;
struct sockaddr_in clientaddr;

client_fd = accept(listen_fd, (struct sockaddr*)&clientaddr, &cliaddr_len);
一般来说都不关心这个长度,这个数据不重要。地址为ipv4类型,所以需要保持一致。内核返回的类型是通用类型的。

5、read()/write()
最后开始进行读写数据

请参考博客:https://blog.csdn.net/weixin_45062087/article/details/118880374

二、client

1、socket()
请参考server部分的socket()

2、connect()
TCP客户端程序调用socket()创建socket fd之后,就可以调用connect()函数来连接服务器。如果客户端这时调用connect()发出连接请求,服务器端就会接收到这个请求并使accept()返回,accept()返回的新的文件描述符就是对应到该客户的TCP连接,通过这两个文件描述符(客户端connect的fd和服务器端accept返回的fd)就可以实现客户端和服务器端的相互通信。

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

sockfd: 客户端的socket()创建的描述字
addr: 要连接的服务器的socket地址信息,这里面包含有服务器的IP地址和端口等信息
addrlen: socket地址的长度

3、read()/write()
最后开始进行读写数据

请参考博客:https://blog.csdn.net/weixin_45062087/article/details/118880374

三、具体代码

1、server

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

//#define PORT 9988
void printf_usage(char *progname)
{
	printf("%s usage: \n",progname);
	printf("-p(--port): sepcify server port \n");
	printf("-h(--help): print this help information \n");
}

int main(int argc, char **argv)
{
	int			socket_fd = -1, client_fd = -1;
	int			rv = -1, rv1 = -1;
	struct sockaddr_in 	serveraddr, clientaddr;
	char			buf[1024];
	socklen_t		clientaddr_len = sizeof(struct sockaddr);
	int			port;
	int			ch;
	int			index;
	struct	option		opts[] = {
		{"port", required_argument, NULL, 'p'},
		{"help", no_argument, NULL, 'h'},
		{NULL, 0, NULL, 0}
	};

	while((ch = getopt_long(argc, argv, "p:h", opts, NULL)) != -1)
	{
		switch(ch)
		{
			case 'p':
				port = atoi(optarg);
				break;

			case 'h':
				printf_usage(argv[0]);
				return 0;
		}
	}

	while(argc < 2)
	{
		printf("please input %s [port] \n",argv[0]);
		return -1;
	}

	socket_fd = socket(AF_INET, SOCK_STREAM, 0);
	if(socket_fd < 0)
	{
		printf("create socket failure : %s \n", strerror(errno));
		return -2;
	}

	port = atoi(argv[1]);
	serveraddr.sin_family =	AF_INET;
	serveraddr.sin_port =	htons(port); //操作系统的字节序为小端,所以需要转换成网络大端字节序
	serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);	//ip地址为4个字节,用htonl;INADDR_ANY(实际上是0)可以自动选择服务器的ip,使用有线网卡就用有线ip,使用无线网卡就用无线ip。
	if(bind(socket_fd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) < 0)
	{
		printf("create server failure : %s \n", strerror(errno));
		return -3;
	}
	printf("socket [%d] bind on port[%d] for all IP address succesfully !", socket_fd, port);

	listen(socket_fd, 10); //监听socket,等待客户端连接,10为正在连接服务端的最大数,超过就不能连入服务器。

	while(1)
	{
		printf("\n Start waiting and accept new client connect...\n");
		client_fd = accept(socket_fd, (struct sockaddr *)&clientaddr, &clientaddr_len); //一般来说不关心addr的长度大小,这个数据不重要;地址为ipv4类型,需要保持一致;内核返回的值为新的socket描述符,用来服务某个接入的客户端。
		if(client_fd < 0)
		{
			printf("accept new socket failture : %s\n", strerror(errno));
			return -4;
		}
		printf("accept new client[%d] socket[%s:%d]\n",client_fd, inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));//inet_ntoa函数为整型转字符型,ntohs函数为大端转小端。
		while(1)
		{
			memset(buf, 0, sizeof(buf));
			rv = read(client_fd, buf, sizeof(buf));
			if(rv < 0)
			{
				printf("read data from client [%d] failure : %s\n", client_fd, strerror(errno));
				close(client_fd);
				break;
			}
			else if(rv == 0)
			{
				printf("%d \n",rv);
				printf("client [%d] disconnected \n", client_fd);
				close(client_fd);
				break;	
			}
			printf("read %d bytes data from client[%d] and the data is : %s \n", rv , client_fd, buf);
		
			rv1 = write(client_fd, buf, rv);
			if(rv1 < 0)
			{
				printf("write data back to client[%d] failure: %s \n", client_fd, strerror(errno));
				close(client_fd);
				break;
			}
		}
		sleep(1);
	}
	close(socket_fd);
}

2、client

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

//#define SERVER_IP	"127.0.0.1"
//#define SERVER_PORT	9988
#define MSG_STR		"Hello,server"

void printf_usage(char *progname)
{
	printf("%s usage: \n",progname);
	printf("-i(--ipaddr): sepcify server IP address \n");
	printf("-p(--port): sepcify server port \n");
	printf("-h(--help): print this help information \n");
}

int main(int argc, char **argv)
{
	int	connt_fd = 	-1;
	int	rv = 		-1;
	struct	sockaddr_in	serveraddr;
	char	buf[1024];
	char	*serverip;
	int	port;
	int	ch;
	int	index;
	struct	option		opts[] = {
		{"ipaddr", required_argument, NULL, 'i'},
		{"port", required_argument, NULL, 'p'},
		{"help", no_argument, NULL, 'h'},
		{NULL, 0, NULL, 0}
	};
	
	while((ch = getopt_long(argc, argv, "i:p:h", opts, NULL)) != -1)
	{
		switch(ch)
		{
			case 'i':
				serverip = optarg;
				break;

			case 'p':
				port = atoi(optarg);
				break;

			case 'h':
				printf_usage(argv[0]);
				return 0;
		}
	}

	while(1)
	{
		while(argc < 3)
		{
			printf("please input %s [serverip] [port]\n",argv[0]);
			return -1;
		}
		connt_fd = socket(AF_INET, SOCK_STREAM, 0);
		if(connt_fd < 0)
		{
			printf("create connt_fd failure: %s!\n", strerror(errno));
			close(connt_fd);
			return -2;
		}
		printf("create connt_fd[%d] successfully! \n", connt_fd);
		//serverip = argv[1];
		//port = atoi(argv[2]);
		memset(&serveraddr, 0 ,sizeof(serveraddr));
		serveraddr.sin_family =	AF_INET;
		serveraddr.sin_port =	htons(port);
		inet_aton(serverip, &serveraddr.sin_addr);	
		if(connect(connt_fd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) < 0)
		{
			printf("connect to server[%s:%d] faiture : %s!\n",serverip, port, strerror(errno));
			close(connt_fd);
			return -3;
		}
		printf("connect to server[%s:%d] successfully!\n", serverip, port);
		while(1)
		{
			if(write(connt_fd, MSG_STR, strlen(MSG_STR)) < 0)
			{
				printf("write the data to buf failture: %s", strerror(errno));
				goto cleanup;
			}	
	
			memset(buf, 0, sizeof(buf));
			rv = read(connt_fd, buf ,sizeof(buf));
			if(rv < 0)
			{
				printf("read the data from server failure: %s", strerror(errno));
				goto cleanup;
			}
	
			if(rv == 0)
			{
				printf("disconnect the server....\n");
				goto cleanup;
			}

			printf("read %d bytes from server : '%s' \n", rv, buf);
			sleep(1);
		}
	
	cleanup:
		close(connt_fd);
	}
}


运行效果:
(1)server
请添加图片描述

请添加图片描述

四、总结

整体来说,需要了解server、client是怎么建立起来的,用了哪些函数。这里通过了一个ipv4和TCP协议建立起双方通信的协议,socket是介于应用层和传输层之间的接口,我们只需要通过系统调用生成socket的文件描述符,用于双方进行通信,有不足之处请各位大佬在评论区留言,谢谢。

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

X 、case

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值