#include
#include
int socket(int domain, int type, int protocol); success:描述符 fail:-1
//domain:协议族 IPv4 AF_INET type:套接字类型:SOCK_STREAM
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
//sockfd:套接字描述符 addr:绑定的地址 addrlen:地址长度 success:0 fail:-1
准备接收连接请求 listen()函数的作用是设置监听上限(同一时刻接收到连接的个数),而不是设置监听。accept()函数才是阻塞监听客户端连接。
int listen(int sockfd, int backlog);
//sockfd:套接字描述符 backlog:请求队列中允许的最大请求数 success: 0 fail:-1
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
//sockfd:套接字描述符 addr:服务器端地址: addrlen:地址长度 success:0 fail:-1
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
//sockfd:套接字描述符 addr:服务器端地址: addrlen:地址长度 success:建立好连接的套接字描述符 fail:-1
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
//sockfd:套接字描述符 buf:发送缓冲区的地址,即结构体地址 len:发送数据的长度 flags:一般为0 success:实际发送的字节数 fail:-1
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
//sockfd:套接字描述符 buf:发送缓冲区的地址,即结构体地址 len:发送数据的长度 flags:一般为0 success:实际接收的字节数 fail:-1
#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。这是
但是一般情况下,该数过于大,一般的程序也就是3~10个描述符。所以一般情况下,选择手动指定。
readset来检查可读性的一组文件描述字。
writeset用来检查可写性的一组文件描述字。
exceptset用来检查是否有异常条件出现的文件描述字。(注:错误不包括在异常条件之内)
timeout=NULL(阻塞:直到有一个fd位被置为1函数才返回)
timeout所指向的结构设为非零时间(等待固定时间:有一个fd位被置为1或者时间耗尽,函数均返回)
timeout所指向的结构,时间设为0(非阻塞:函数检查完每个fd后立即返回)
稍后会在代码实例中说明使用方法。
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;
}