基于Linux的socket网络编程(select IO多路复用)

基于Linux的socket网络编程(select IO多路复用)

  • 一、基本函数
    • 1、socket创建
    • 2、bind:与套接字进行绑定
    • 3、listen 监听模式
    • 4、connect:客户端发送连接请求
    • 5、accept:接受客户端连接请求
    • 6、send:发送数据
    • 7、recv:接收数据
    • 8、fd_set 与select
  • 二、socket编程流程
  • 三、代码实例

一、基本函数

1、socket创建

#include           
#include 
int socket(int domain, int type, int protocol);  success:描述符  fail:-1
//domain:协议族 IPv4 AF_INET  type:套接字类型:SOCK_STREAM

2、bind:与套接字进行绑定

int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
//sockfd:套接字描述符  addr:绑定的地址  addrlen:地址长度  success:0  fail:-1

3、listen 监听模式

准备接收连接请求 listen()函数的作用是设置监听上限(同一时刻接收到连接的个数),而不是设置监听。accept()函数才是阻塞监听客户端连接。

int listen(int sockfd, int backlog);
//sockfd:套接字描述符  backlog:请求队列中允许的最大请求数  success: 0  fail:-1

4、connect:客户端发送连接请求

int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
//sockfd:套接字描述符  addr:服务器端地址: addrlen:地址长度 success:0  fail:-1

5、accept:接受客户端连接请求

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
//sockfd:套接字描述符  addr:服务器端地址: addrlen:地址长度 success:建立好连接的套接字描述符  fail:-1

6、send:发送数据

ssize_t send(int sockfd, const void *buf, size_t len, int flags);
//sockfd:套接字描述符  buf:发送缓冲区的地址,即结构体地址  len:发送数据的长度  flags:一般为0  success:实际发送的字节数  fail:-1

7、recv:接收数据

ssize_t recv(int sockfd, void *buf, size_t len, int flags);
//sockfd:套接字描述符  buf:发送缓冲区的地址,即结构体地址  len:发送数据的长度  flags:一般为0  success:实际接收的字节数  fail:-1

8、fd_set 与select

#include 
#include 
#include 
int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
void FD_CLR(int fd, fd_set *set);
int  FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);

fd_set结合select()判断句柄状态
fd_set是一个长度为64的数组
fd_set set;
FD_ZERO(&set); /* 将set清零使集合中不含任何fd*/
FD_SET(fd, &set); /* 将fd加入set集合 /
FD_CLR(fd, &set); /
将fd从set集合中清除 /
FD_ISSET(fd, &set); /
测试fd是否在set集合中*/
FD_ZERO就是把当前fd_set所有位的数字都置为0。
FD_SET实现了句柄和fd_set的联系,可以把fd(FD_SET(int fd, fd_set *fdset); // 将fd加入set集合),也就是句柄/文件描述符加入到fd_set中。
FD_CLR清除所绑定的联系,这里只清除你传进去的fd和fd_set之间的联系。
需要注意的是,FD_CLR的操作类似于链表节点的删除(后续节点会填补被删除节点)。

int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);

功能:测试指定的fd可读、可写、有异常条件待处理
select()的第一个参数nfds的意思是“最大文件描述符编号加1”,
需要检查的文件描述字个数(即检查到fd_set的第几位),数值应该比三组fd_set中所含的最大fd值更大,一般设为三组fd_set中所含的最大fd值加1
(如在readset,writeset,exceptset中所含最大的fd为5,则nfds=6,因为fd是从0开始的)。设这个值是为提高效率,使函数不必检查fd_set的所有1024位。
设置为FD_SETSIZE。这是的一个常量,它指定最大描述符数(通常是1024)。
但是一般情况下,该数过于大,一般的程序也就是3~10个描述符。所以一般情况下,选择手动指定。
readset来检查可读性的一组文件描述字。
writeset用来检查可写性的一组文件描述字。
exceptset用来检查是否有异常条件出现的文件描述字。(注:错误不包括在异常条件之内)
timeout=NULL(阻塞:直到有一个fd位被置为1函数才返回)
timeout所指向的结构设为非零时间(等待固定时间:有一个fd位被置为1或者时间耗尽,函数均返回)
timeout所指向的结构,时间设为0(非阻塞:函数检查完每个fd后立即返回)
稍后会在代码实例中说明使用方法。

二、socket编程流程

TCP socket编程基本流程:
客户端:socket----------------------connect----send------recv----close
服务器:socket—bind—listen-----accept-----recv-----send—close
服务器需要先运行 进行socket、bind、listen、accept后,服务器阻塞,等待客户端连接;客户端后运行,进行socket、connect流程以连接服务器。

三、代码实例

服务器端样例:支持多个同时连接 但不支持同时通信(没有子进程/子线程)
creatlink连接子函数:

#include "../include/server.h"

int creatlink(const char *argv[])
{
	int sockfd = socket(AF_INET,SOCK_STREAM,0);
	if(sockfd < 0)
	{
		perror("socket error");
		return -1;
	}
	printf("sock ok\n");

	struct sockaddr_in seraddr;
	seraddr.sin_family = AF_INET;
	seraddr.sin_port = htons(atoi(argv[2]));
	seraddr.sin_addr.s_addr = inet_addr(argv[1]);
	int len = sizeof(seraddr);

	int ret = bind(sockfd,(struct sockaddr *)&seraddr,len);
	if(ret < 0)
	{
		perror("bind error");
		exit(-1);
	}
	printf("bind ok\n");

	ret = listen(sockfd,5);
	if(ret < 0)
	{
		perror("listen error");
		exit(-1);
	}
	printf("listen...\n");

	return sockfd;
}

服务端主函数

#include "../include/server.h"

int main(int argc,const char *argv[])
{
	if(argc != 3)
	{
		printf("CMDLINE NOT EQUAL THREE!\n");
		return -1;
	}
	int sockfd = creatlink(argv);
	struct sockaddr_in cliaddr;
	int len = sizeof(cliaddr);

	fd_set rest,allset;
	FD_ZERO(&rest);
	FD_ZERO(&allset);
	FD_SET(sockfd,&allset);
	int maxfd = sockfd;
	while(1)
	{
		rest = allset;
		if(select(maxfd+1,&rest,NULL,NULL,NULL) < 0)
		{
			printf("select error\n");
			exit(-1);
		}
		if(FD_ISSET(sockfd,&rest))
		{
			int confd = accept(sockfd,(struct sockaddr *)&cliaddr,&len);
			if(confd < 0)
			{
				perror("accept error");
				exit(-1);
			}
			printf("accept ok\n");
			FD_SET(confd,&allset);
			if(confd > maxfd)
				maxfd = confd;
		}
		else
		{
			for(int i = 0;i < maxfd+1;i++)
			{
				if(FD_ISSET(i,&rest))
				{
					int ret = srecv(i); //接收子函数
					if(ret == 0)
					{
						printf("closed client\n");
						exit(-1);
					}
				}
			}
		}
	}
}

将监听产生的listenfd加入fd_set集合中,使用select函数判断有无可读事件产生,可读事件有两种情况,第一种是有新的客户端连接,第二种是文件描述符有数据可读,即客户端发来了消息,当可读事件产生时,rest集合中会只剩下产生可读事件的文件描述符/监听套接字;此时使用FD_ISSET判断监听套接字是否在rest集合中,如果是,即新客户端连接,使用accept直接接收并建立连接(此时已经完成三次握手,accept不阻塞),然后把accept产生的connect fd加入到allset集合中即可;若不是,即客户端传送了数据,调用recv函数即可。

客户端样例:
creatlink子函数 在主函数中直接调用即可

int creatlink(const char *argv[])
{
	int sockfd = socket(AF_INET,SOCK_STREAM,0);
	if(sockfd < 0)
	{
		perror("socket error");
		exit(-1);
	}
	printf("socket ok\n");
	struct sockaddr_in servaddr;
	bzero(&servaddr,sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_port = htons(atoi(argv[2]));
	servaddr.sin_addr.s_addr = inet_addr(argv[1]);
	int ret = connect(sockfd,(struct sockaddr *)&servaddr,sizeof(servaddr));
	if(ret < 0)
	{
		perror("connect error");
		exit(-1);
	}
	printf("connect ok\n");
	return sockfd;
}

你可能感兴趣的:(C语言,网络,linux,c语言)