B/S C/S
OSI七层,TCP/IP四层
数据包
以太网帧
ARP数据
IP
UDP: 无连接的不可靠报文传输
TCP:面向连接的可靠数据包传输
三次握手和四次握手标志位(主要是存在半关闭状态,所以断开连接时,收到确认断开和发起断开请求不一定在一起)
请求连接SYN,应答ACK,请求断开FIN
状态转换图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UYju395O-1642467862986)(G:\学习资料\linux服务器开发三-网络编程\image-20220104192118868.png)]
实线(客户端流程):客户端主动发起连接,客户端变为SYN_SENT,等待对端SYN,ACK,然后发送ACK,状态变成数据传输状态ESTABLISHED,关闭连接时,客户端发送FIN,状态变成FIN_WAIT_1;服务端闲时回复ACK,客户端变成FIN_WAIT_2,半关闭状态;服务端发送FIN到客户端,客户端发送ACK后,客户端变成TIME_WAIT状态,此时客户端等待2MSL超时时长(linux一般一分钟左右),确保服务端可以接受到ACK;
虚线(服务端流程):服务端调用accept,状态由CLOSE变成LISTEN,客户端发送请求,服务端回复SYN+ACK,状态变成SYN_RCVD,客户端回复一个ACK,服务端接收到了后变成ESTABLISHED状态;关闭时,接受到客户端FIN并回复ACK,状态变成CLOSE_WAIT;服务端准备好了,发送FIN,状态变为LAST_ACK;
细线:客户端处于SYN_SENT时,发送连接请求并且服务端很快回复了SYN+ACK,基本同时发生;当两端同时关闭时,即同时发送FIN和ACK,可能进入CLOSING,更接近时可能直接进入TIME_WAIT阶段。
路由
路由表
hub工作原理
MTU
一般主机使用的是小端字节序,即低地址存储地位字节,高地址存储高位字节,而网络传输使用的大端字节序刚好相反;如十六进制数 0x03e8需要两个字节存储,03为高位,e8为地位,在小端字节序中,03存储到地址1,e8存储到地址0。
所以发送和接收网络数据时都应该进行字节序的转换。
#include
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位短整数。
如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回,如果主机是大端字节序,这些函数不做转换,将参数原封不动地返回。
ip地址的转换还可以使用inet_pton函数:
#include
int inet_pton(int af, const char *src, void *dst);
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
网络套接字函数使用的结构体sockaddr和sockaddr_in
struct sockaddr {
sa_family_t sa_family; /* address family, AF_xxx */
char sa_data[14]; /* 14 bytes of protocol address */
};
struct sockaddr_in {
__kernel_sa_family_t sin_family; /* Address family */ 地址结构类型
__be16 sin_port; /* Port number */ 端口号
struct in_addr sin_addr; /* Internet address */ IP地址
};
struct in_addr { /* Internet address. */
__be32 s_addr;
};
历史原因,函数使用的结构体为sockaddr,但是我们只能定义sockaddr_in;所以需要在使用函数时转换类型。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZPnUtd7S-1642467862987)(G:\学习资料\linux服务器开发三-网络编程\image-20211228193356349.png)]
#include /* See NOTES */
#include
int socket(int domain, int type, int protocol);
domain:
AF_INET 这是大多数用来产生socket的协议,使用TCP或UDP来传输,用IPv4的地址
AF_INET6 与上面类似,不过是来用IPv6的地址
AF_UNIX 本地协议,使用在Unix和Linux系统上,一般都是当客户端和服务器在同一台及其上的时候使用
type:
SOCK_STREAM 这个协议是按照顺序的、可靠的、数据完整的基于字节流的连接。这是一个使用最多的socket类型,这个socket是使用TCP来进行传输。
SOCK_DGRAM 这个协议是无连接的、固定长度的传输调用。该协议是不可靠的,使用UDP来进行它的连接。
SOCK_SEQPACKET该协议是双线路的、可靠的连接,发送固定长度的数据包进行传输。必须把这个包完整的接受才能进行读取。
SOCK_RAW socket类型提供单一的网络访问,这个socket类型使用ICMP公共协议。(ping、traceroute使用该协议)
SOCK_RDM 这个类型是很少使用的,在大部分的操作系统上没有实现,它是提供给数据链路层使用,不保证数据包的顺序
protocol:
传0 表示使用默认协议。
返回值:
成功:返回指向新创建的socket的文件描述符,失败:返回-1,设置errno
socket()打开一个网络通讯端口,如果成功的话,就像open()一样返回一个文件描述符,应用程序可以像读写文件一样用read/write在网络上收发数据,如果socket()调用出错则返回-1。
对于IPv4,domain参数指定为AF_INET。对于TCP协议,type参数指定为SOCK_STREAM,表示面向流的传输协议。如果是UDP协议,则type参数指定为SOCK_DGRAM,表示面向数据报的传输协议。
protocol参数的介绍从略,指定为0即可。
#include /* See NOTES */
#include
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
sockfd:
socket文件描述符
addr:
构造出IP地址+端口号
addrlen:
sizeof(addr)长度
返回值:
成功返回0,失败返回-1, 设置errno
服务器程序所监听的网络地址和端口号通常是固定不变的,客户端程序得知服务器程序的地址和端口号后就可以向服务器发起连接,因此服务器需要调用bind绑定一个固定的网络地址和端口号。
bind()的作用是将参数sockfd和addr绑定在一起,使sockfd这个用于网络通讯的文件描述符监听addr所描述的地址和端口号。前面讲过,struct sockaddr *是一个通用指针类型,addr参数实际上可以接受多种协议的sockaddr结构体,而它们的长度各不相同,所以需要第三个参数addrlen指定结构体的长度。如:
struct sockaddr_in servaddr;
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(6666);
首先将整个结构体清零,然后设置地址类型为AF_INET,网络地址为INADDR_ANY,这个宏表示本地的任意IP地址,因为服务器可能有多个网卡,每个网卡也可能绑定多个IP地址,这样设置可以在所有的IP地址上监听,直到与某个客户端建立了连接时才确定下来到底用哪个IP地址,端口号为6666。
#include /* See NOTES */
#include
int listen(int sockfd, int backlog);
sockfd:
socket文件描述符
backlog:
排队建立3次握手队列和刚刚建立3次握手队列的链接数和
典型的服务器程序可以同时服务于多个客户端,当有客户端发起连接时,服务器调用的accept()返回并接受这个连接,如果有大量的客户端发起连接而服务器来不及处理,尚未accept的客户端就处于连接等待状态,listen()声明sockfd处于监听状态,并且最多允许有backlog个客户端处于连接待状态,如果接收到更多的连接请求就忽略。listen()成功返回0,失败返回-1。
listen函数并不是监听,而是设置允许同时建立连接的个数。
#include /* See NOTES */
#include
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
sockdf:
socket文件描述符
addr:
传出参数,返回链接客户端地址信息,含IP地址和端口号
addrlen:
传入传出参数(值-结果),传入sizeof(addr)大小,函数返回时返回真正接收到地址结构体的大小
返回值:
成功返回一个新的socket文件描述符,用于和客户端通信,失败返回-1,设置errno
三方握手完成后,服务器调用accept()接受连接,如果服务器调用accept()时还没有客户端的连接请求,就阻塞等待直到有客户端连接上来。addr是一个传出参数,accept()返回时传出客户端的地址和端口号。addrlen参数是一个传入传出参数(value-result argument),传入的是调用者提供的缓冲区addr的长度以避免缓冲区溢出问题,传出的是客户端地址结构体的实际长度(有可能没有占满调用者提供的缓冲区)。如果给addr参数传NULL,表示不关心客户端的地址。
程序的结果如下:
while (1) {
cliaddr_len = sizeof(cliaddr);
connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);
n = read(connfd, buf, MAXLINE);
......
close(connfd);
}
整个是一个while死循环,每次循环处理一个客户端连接。由于cliaddr_len是传入传出参数,每次调用accept()之前应该重新赋初值。accept()的参数listenfd是先前的监听文件描述符,而accept()的返回值是另外一个文件描述符connfd,之后与客户端之间就通过这个connfd通讯,最后关闭connfd断开连接,而不关闭listenfd,再次回到循环开头listenfd仍然用作accept的参数。accept()成功返回一个文件描述符,出错返回-1。
#include /* See NOTES */
#include
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
sockdf:
socket文件描述符
addr:
传入参数,指定服务器端地址信息,含IP地址和端口号
addrlen:
传入参数,传入sizeof(addr)大小
返回值:
成功返回0,失败返回-1,设置errno
客户端需要调用connect()连接服务器,connect和bind的参数形式一致,区别在于bind的参数是自己的地址,而connect的参数是对方的地址。connect()成功返回0,出错返回-1。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yKwr1SOs-1642467862987)(G:\学习资料\linux服务器开发三-网络编程\image-20211228200554919.png)]
服务器调用socket()、bind()、listen()完成初始化后,调用accept()阻塞等待,处于监听端口的状态,客户端调用socket()初始化后,调用connect()发出SYN段并阻塞等待服务器应答,服务器应答一个SYN-ACK段,客户端收到后从connect()返回,同时应答一个ACK段,服务器收到后从accept()返回。
数据传输的过程:
建立连接后,TCP协议提供全双工的通信服务,但是一般的客户端/服务器程序的流程是由客户端主动发起请求,服务器被动处理请求,一问一答的方式。因此,服务器从accept()返回后立刻调用read(),读socket就像读管道一样,如果没有数据到达就阻塞等待,这时客户端调用write()发送请求给服务器,服务器收到后从read()返回,对客户端的请求进行处理,在此期间客户端调用read()阻塞等待服务器的应答,服务器调用write()将处理结果发回给客户端,再次调用read()阻塞等待下一条请求,客户端收到后从read()返回,发送下一条请求,如此循环下去。
如果客户端没有更多的请求了,就调用close()关闭连接,就像写端关闭的管道一样,服务器的read()返回0,这样服务器就知道客户端关闭了连接,也调用close()关闭连接。注意,任何一方调用close()后,连接的两个传输方向都关闭,不能再发送数据了。如果一方调用shutdown()则连接处于半关闭状态,仍可接收对方发来的数据。
在学习socket API时要注意应用程序和TCP协议层是如何交互的: 应用程序调用某个socket函数时TCP协议层完成什么动作,比如调用connect()会发出SYN段 应用程序如何知道TCP协议层的状态变化,比如从某个阻塞的socket函数返回就表明TCP协议收到了某些段,再比如read()返回0就表明收到了FIN段
server.c的作用是从客户端读字符,然后将每个字符转换为大写并回送给客户端。
#include
#include
#include
#include
#include
#include
#include
#define MAXLINE 80
#define SERV_PORT 6666
int main(void)
{
struct sockaddr_in servaddr, cliaddr;
socklen_t cliaddr_len;
int listenfd, connfd;
char buf[MAXLINE];
char str[INET_ADDRSTRLEN];
int i, n;
listenfd = socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
int opt = 1;
setsockopt(sfd,SOL_SOCKET,SO_REUSEADDR,&opt, sizeof(opt));//允许端口复用,解决2msl的问题
bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
listen(listenfd, 20);
printf("Accepting connections ...\n");
while (1) {
cliaddr_len = sizeof(cliaddr);
connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);
n = read(connfd, buf, MAXLINE);
printf("received from %s at PORT %d\n",
inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
ntohs(cliaddr.sin_port));
for (i = 0; i < n; i++)
buf[i] = toupper(buf[i]);
write(connfd, buf, n);
close(connfd);
}
return 0;
}
下面是自己测试的
#include
#include
#include
#include
#include
#include
#define SERV_PORT 6666//一般使用3000以上
#define SERV_IP "127.0.0.1"
int main(){
int lfd, cfd,i;
struct sockaddr_in serv_addr,client_addr;
socklen_t clie_addr_len;
char buf[BUFSIZ], client_IP[BUFSIZ];
int n;
lfd = socket(AF_INET, SOCK_STREAM, 0);
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(SERV_PORT);
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
bind(lfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
listen(lfd, 128);
clie_addr_len = sizeof(client_addr);
cfd = accept(lfd,(struct sockaddr*)&client_addr,&clie_addr_len);
printf("client ip=%s, port=%d\n",inet_ntop(AF_INET, &client_addr.sin_addr.s_addr,&client_IP,sizeof(client_IP)),ntohs(client_addr.sin_port));
while(1){
n = read(cfd, buf, sizeof(buf));
for(i = 0; i < n; i++){
buf[i] = toupper(buf[i]);
}
write(cfd,buf,n);
}
close(lfd);
close(cfd);
return 0;
}
通过另外一个cmd控制台输入命令nc 127.0.0.1 666
进行通讯连接,发送数据,返回大写。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZetdQk1S-1642467862987)(G:\学习资料\linux服务器开发三-网络编程\image-20211229082245968.png)]
客户端可以不调用bind,系统会隐式绑定。
#include
#include
#include
#include
#include
#define SERV_IP "127.0.0.1"
#define SERV_PORT 6666
int main(){
int cfd;
struct sockaddr_in serv_addr;
// socklen_t serv_addr_len;
char buf[BUFSIZ];
int n;
cfd = socket(AF_INET, SOCK_STREAM, 0);
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(SERV_PORT);
inet_pton(AF_INET, SERV_IP, &serv_addr.sin_addr.s_addr);
printf("before connect\n");
connect(cfd, (struct sockaddr*)&serv_addr,sizeof(serv_addr));
printf("after connect\n");
while(1){
fgets(buf, sizeof(buf), stdin);
write(cfd, buf, sizeof(buf));
n = read(cfd, buf, sizeof(buf));
// write(STDOUT_FILENO, buf, n);
printf("%s\n",buf);
}
close(cfd);
return 0;
}
读指定字节数,每次发送的帧可能不够数组的总长度,需要多次读,这样就需要拼接了,如,数据长度为4096个字节,但是每次数据发送的最大长度限制到了1500字节,那么就需要读四次。
ssize_t是有符号整型;
//socket套接字,接收存储的缓存区指针,接收缓存区的大小
ssize_t Readn(int fd, void *vptr, size_t n)
{
size_t nleft; // unsigned int 剩余的未读取的字节数
ssize_t nread; // int 实际读到的字节数
char *ptr;
ptr = vptr;
nleft = n; //n 未读取的字节数,初始化为缓存区大小
while(nleft > 0)
{
if ((nread = read(fd, ptr, nleft)) < 0)
{
if(errno == EINTR)
{
nread = 0;
} else if (nread == 0)
break;
}
nleft -= nread;//记录剩余 空间 大小
ptr += nread;//指针先后移,继续下次向后面 读取完存储
}
return n - nleft;
}
读取一行
//因为是静态声明,所以局部变量可以保持不变,不清空?
static ssize_t my_read(int fd, char *ptr)
{
static int read_cnt;//可以没有初始值吗
static char *read_ptr;
static char read_buf[100];
if (read_cnt <= 0)
{
again:
if((read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0)
{//错误处理
if (errno == EINTER)
goto again;
return -1;
} else if (read_cnt == 0)
return 0;
read_ptr = read_buf;
}
read_cnt--;
*ptr = *read_ptr++;//每次调用取出一个字节数据,没有了再read
return 1;
}
ssize_t readline(int fd, void *vptr, size_t maxlen)
{
ssize_t n, rc;
char c,*ptr;
ptr = vptr;
for(n = 1; n <maxlen; n++)
{
if ((rc = my_read(fd, &c)) == 1)
{
*ptr++ = c;
if(c == '\n')//读取结束
{
break;
}
}
else if (rc == 0) //读取结束
{
*ptr = 0;
return n - 1;
}
else // 错误
{
return -1;
}
}
*ptr = 0;
return n;
}
//hserver.c
#include
#include
#include
#include
#include
#include
#include
#include"wrap.h"
#include
#define SERV_IP "127.0.0.1"
#define SERV_PORT 6666
void wait_child(int signo)
{
while(waitpid(0, NULL, WNOHANG) > 0);
return;
}
int main()
{
pid_t pid;
int sfd, cfd, i, n;
struct sockaddr_in server_addr, client_addr;
socklen_t client_addr_len;
char buf[BUFSIZ], ip[BUFSIZ];
sfd = Socket(AF_INET, SOCK_STREAM, 0);
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERV_PORT);
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
// inet_pton(AF_INET, SERV_IP, &server_addr.sin_addr.s_addr);
Bind(sfd, (struct sockaddr*)&server_addr, sizeof(server_addr));
Listen(sfd, 128);
client_addr_len = sizeof(client_addr);
while(1){//父进程循环处理等待接收
cfd = Accept(sfd, (struct sockaddr*)&client_addr, &client_addr_len);
printf("client ip = %s, port=%d\n",inet_ntop(AF_INET,&client_addr.sin_addr.s_addr,ip, sizeof(ip)),ntohs(client_addr.sin_port));
pid = fork();
if(pid < 0){
perror("fork");
exit(1);
} else if (pid == 0){//子进程跳出 之后后面程序
close(sfd);
break;
}
close(cfd);
printf("child pid=%d\n",pid);
signal(SIGCHLD, wait_child);
}
if( pid == 0){//子进程 处理
while(1){
n = Read(cfd, buf, sizeof(buf));
if(n == 0){
printf("child close pid=%d\n",getpid());
close(cfd);
return 0;
} else if( n == -1 ){
perror("read");
exit(1);
} else {
for(i = 0; i < n; i++){
buf[i] = toupper(buf[i]);
}
write(cfd, buf, n);
}
}
}
return 0;
}
fork时的fd是有关联的,但fork之后各自再新建的fd,即使值相同,也是相互没有关联的。 所以fork之后,创建其他fd之前,应该马上对各自的fd进行处理,父进程关闭accept到的fd,子进程关闭listen的fd;至于每次都是相同fd,这个没关系的,不同的进程,相同的fd是相互不会影响的,进程是相互隔离的
以下为封装后的socket函数
//wrap.c
#include
#include
#include
void perr_exit(const char *s)
{
perror(s);
exit(1);
}
int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr)
{
int n;
again:
if ( (n = accept(fd, sa, salenptr)) < 0) {
if ((errno == ECONNABORTED) || (errno == EINTR))
goto again;
else
perr_exit("accept error");
}
return n;
}
int Bind(int fd, const struct sockaddr *sa, socklen_t salen)
{
int n;
if ((n = bind(fd, sa, salen)) < 0)
perr_exit("bind error");
return n;
}
int Connect(int fd, const struct sockaddr *sa, socklen_t salen)
{
int n;
if ((n = connect(fd, sa, salen)) < 0)
perr_exit("connect error");
return n;
}
int Listen(int fd, int backlog)
{
int n;
if ((n = listen(fd, backlog)) < 0)
perr_exit("listen error");
return n;
}
int Socket(int family, int type, int protocol)
{
int n;
if ( (n = socket(family, type, protocol)) < 0)
perr_exit("socket error");
return n;
}
ssize_t Read(int fd, void *ptr, size_t nbytes)
{
ssize_t n;
again:
if ( (n = read(fd, ptr, nbytes)) == -1) {
if (errno == EINTR)
goto again;
else
return -1;
}
return n;
}
ssize_t Write(int fd, const void *ptr, size_t nbytes)
{
ssize_t n;
again:
if ( (n = write(fd, ptr, nbytes)) == -1) {
if (errno == EINTR)
goto again;
else
return -1;
}
return n;
}
int Close(int fd)
{
int n;
if ((n = close(fd)) == -1)
perr_exit("close error");
return n;
}
ssize_t Readn(int fd, void *vptr, size_t n)
{
size_t nleft;
ssize_t nread;
char *ptr;
ptr = vptr;
nleft = n;
while (nleft > 0) {
if ( (nread = read(fd, ptr, nleft)) < 0) {
if (errno == EINTR)
nread = 0;
else
return -1;
} else if (nread == 0)
break;
nleft -= nread;
ptr += nread;
}
return n - nleft;
}
ssize_t Writen(int fd, const void *vptr, size_t n)
{
size_t nleft;
ssize_t nwritten;
const char *ptr;
ptr = vptr;
nleft = n;
while (nleft > 0) {
if ( (nwritten = write(fd, ptr, nleft)) <= 0) {
if (nwritten < 0 && errno == EINTR)
nwritten = 0;
else
return -1;
}
nleft -= nwritten;
ptr += nwritten;
}
return n;
}
static ssize_t my_read(int fd, char *ptr)
{
static int read_cnt;
static char *read_ptr;
static char read_buf[100];
if (read_cnt <= 0) {
again:
if ((read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0) {
if (errno == EINTR)
goto again;
return -1;
} else if (read_cnt == 0)
return 0;
read_ptr = read_buf;
}
read_cnt--;
*ptr = *read_ptr++;
return 1;
}
ssize_t Readline(int fd, void *vptr, size_t maxlen)
{
ssize_t n, rc;
char c, *ptr;
ptr = vptr;
for (n = 1; n < maxlen; n++) {
if ( (rc = my_read(fd, &c)) == 1) {
*ptr++ = c;
if (c == '\n')
break;
} else if (rc == 0) {
*ptr = 0;
return n - 1;
} else
return -1;
}
*ptr = 0;
return n;
}
头文件
wrap.h
#ifndef __WRAP_H_
#define __WRAP_H_
void perr_exit(const char *s);
int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr);
int Bind(int fd, const struct sockaddr *sa, socklen_t salen);
int Connect(int fd, const struct sockaddr *sa, socklen_t salen);
int Listen(int fd, int backlog);
int Socket(int family, int type, int protocol);
ssize_t Read(int fd, void *ptr, size_t nbytes);
ssize_t Write(int fd, const void *ptr, size_t nbytes);
int Close(int fd);
ssize_t Readn(int fd, void *vptr, size_t n);
ssize_t Writen(int fd, const void *vptr, size_t n);
ssize_t my_read(int fd, char *ptr);
ssize_t Readline(int fd, void *vptr, size_t maxlen);
#endif
编译
gcc wrap.c -c -Wall
gcc hserver.c -c -Wall
gcc wrap.o hserver.o -o -Wall
//hservert.c
#include
#include
#include
#include
#include
#include "wrap.h"
#define MAXLINE 80
#define SERV_PORT 6666
struct s_info {
struct sockaddr_in cliaddr;
int connfd;
};
void *do_work(void *arg)
{
int n,i;
struct s_info *ts = (struct s_info*)arg;
char buf[MAXLINE];
char str[INET_ADDRSTRLEN];
/* 可以在创建线程前设置线程创建属性,设为分离态,哪种效率高内? */
pthread_detach(pthread_self());
while (1) {
n = Read(ts->connfd, buf, MAXLINE);
if (n == 0) {
printf("the other side has been closed.\n");
break;
}
printf("received from %s at PORT %d\n",
inet_ntop(AF_INET, &(*ts).cliaddr.sin_addr, str, sizeof(str)),
ntohs((*ts).cliaddr.sin_port));
for (i = 0; i < n; i++)
buf[i] = toupper(buf[i]);
Write(ts->connfd, buf, n);
}
Close(ts->connfd);
}
int main(void)
{
struct sockaddr_in servaddr, cliaddr;
socklen_t cliaddr_len;
int listenfd, connfd;
int i = 0;
pthread_t tid;
struct s_info ts[256];
listenfd = Socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
Listen(listenfd, 20);
printf("Accepting connections ...\n");
while (1) {
cliaddr_len = sizeof(cliaddr);
connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);
ts[i].cliaddr = cliaddr;
ts[i].connfd = connfd;
/* 达到线程最大数时,pthread_create出错处理, 增加服务器稳定性 */
pthread_create(&tid, NULL, do_work, (void*)&ts[i]);
i++;
}
return 0;
}
多线程与多进程处理连接请求,全部由用户程序执行,消耗cpu比较严重。
最大监听1024个文件描述符。
#include
#include
#include
#include
#include
#include
#include"wrap.h"
#define SERV_PORT 6666
int main(int argc, char* argv[])
{
int i, j, n, maxi;
int nready, client[FD_SETSIZE];//存储客户端套接字
int maxfd, listenfd, connfd, sockfd;
char buf[BUFSIZ],str[INET_ADDRSTRLEN];//INET_ADDRSTRLEN = 16
struct sockaddr_in clie_addr, serv_addr;
socklen_t clie_addr_len;
fd_set rset, allset;
listenfd = Socket(AF_INET, SOCK_STREAM, 0);
bzero(&serv_addr, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(SERV_PORT);
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
Bind(listenfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
Listen(listenfd, 128);
clie_addr_len = sizeof(clie_addr);
maxfd = listenfd;//最大的套接字
maxi = -1;
for (i = 0; i < FD_SETSIZE; i++)
{
client[i] = -1;
}
FD_ZERO(&allset);//清空监听的集合
FD_SET(listenfd, &allset);//添加服务端套接字
while(1){
rset = allset;//用于select传入传出参数
nready = select(maxfd + 1, &rset, NULL, NULL, NULL);//不监听写与异常文件描述符集,没有返回则一直等待
if(nready < 0){
perr_exit("select error");
}
if(FD_ISSET(listenfd, &rset)){//是否有客户端连接
connfd = Accept(listenfd, (struct sockaddr*)&clie_addr, &clie_addr_len);
printf("client connnect ip=%s, port=%d\n",inet_ntop(AF_INET, &clie_addr.sin_addr.s_addr, str, INET_ADDRSTRLEN),ntohs(clie_addr.sin_port));
for (i = 0; i < FD_SETSIZE; i++)//保存新连接的客户端套接字
{
if( client[i] < 0){
client[i] = connfd;
break;
}
}
if( i == FD_SETSIZE){//连接过多报错
printf("too many client\n");
exit(1);
}
FD_SET(connfd, &allset);//添加新客户端套接字,更新监视集
if(connfd > maxfd){
maxfd = connfd;//更新最大的套接字(文件描述符)
}
if(i > maxi){
maxi = i;//maxi是client的最后一个套接字下标
}
if(--nready == 0)//没有其他连接请求,跳出循环
continue;
}
for (i = 0; i <= maxi; i++)//循环所有客户端连接
{
sockfd = client[i];
if(sockfd < 0){//client里面存储的空fd
continue;
}
if(FD_ISSET(sockfd, &rset)){
if((n = Read(sockfd, buf, sizeof(buf))) == 0){//客户端断开
Close(sockfd);
FD_CLR(sockfd, &allset);
client[i] = -1;
} else if(n > 0){
printf("client send %s\n",buf);
for (j = 0; j < n; j++)
{
buf[j] = toupper(buf[j]);
}
Write(sockfd, buf, n);
}
if(--nready == 0)//跳出处理连接客户端循环
break;
}
}
}
Close(listenfd);
return 0;
}
针对linux系统,文件描述符上限可以大于1024(相较于select)
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include"wrap.h"
#define MAXLINE 80
#define SERV_PORT 6666
#define OPEN_MAX 1024
int main(int argc, char* argv[])
{
int i, j, maxi, listenfd, connfd, sockfd;
int nready;
ssize_t n;
char buf[BUFSIZ],clientip[INET_ADDRSTRLEN];
struct sockaddr_in serv_addr, clie_addr;
socklen_t clie_addr_len;
struct pollfd client[OPEN_MAX];
listenfd = Socket(AF_INET, SOCK_STREAM, 0);
int opt = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
bzero(&serv_addr, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(SERV_PORT);
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
Bind(listenfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
clie_addr_len = sizeof(clie_addr);
Listen(listenfd, 128);
client[0].fd = listenfd;
client[0].events = POLLRDNORM;
for (i = 1; i < OPEN_MAX; i++)
{
client[i].fd = -1;
}
maxi = 0;
while(1)
{
nready = poll(client, maxi + 1, -1);//阻塞监听
if(client[0].revents & POLLRDNORM)
{
connfd = Accept(listenfd, (struct sockaddr*)&clie_addr, &clie_addr_len);
printf("client connect ip=%s,port=%d\n",inet_ntop(AF_INET, &clie_addr.sin_addr.s_addr, clientip, sizeof(clientip)), ntohs(clie_addr.sin_port));
for (i = 0; i < OPEN_MAX; i++)
{
if(client[i].fd < 0)
{
client[i].fd = connfd;
client[i].events = POLLRDNORM;
break;
}
}
if(i == OPEN_MAX){
perr_exit("too many connect\n");
break;
}
if(i > maxi)
maxi = i;
if(--nready == 0)
continue;
}
for (i = 0; i <= maxi; i++)
{
sockfd = client[i].fd;
if(sockfd < 0){
continue;
}
if(client[i].revents & (POLLRDNORM | POLLERR))
{
n = Read(sockfd, buf, sizeof(buf));
if(n < 0){
if(errno == ECONNRESET)
{
printf("client aborted\n");
Close(sockfd);
client[i].fd = -1;
}else{
perr_exit("read erron");
}
} else if (n == 0){
printf("client close\n");
Close(sockfd);
client[i].fd = -1;
continue;
} else {
for (j = 0; j < n; j++)
{
buf[j] = toupper(buf[j]);
}
Write(sockfd, buf, n);
}
if(--nready <= 0)
break;
}
}
}
Close(listenfd);
return 0;
}
int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event* event);
int epoll_wait(int epfd, struct epoll_event* events,int maxevents, int timeout);
#include
#include
#include
#include
#include
#include
#include"wrap.h"
#include
#include
#include
#define MAXLINE 8192
#define SERV_PORT 6666
#define OPEN_MAX 5000
int main(int argc, char* argv[])
{
int i, j, listenfd, connfd;
int n;
ssize_t nready, efd, res;
char buf[MAXLINE],str[INET_ADDRSTRLEN];
socklen_t clilen;
struct sockaddr_in seraddr, cliaddr;
struct epoll_event tep, ep[OPEN_MAX];
listenfd = Socket(AF_INET, SOCK_STREAM, 0);
int opt = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
bzero(&seraddr, sizeof(seraddr));
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(SERV_PORT);
seraddr.sin_addr.s_addr = htonl(INADDR_ANY);
Bind(listenfd, (struct sockaddr*)&seraddr, sizeof(seraddr));
Listen(listenfd, 128);
clilen = sizeof(cliaddr);
efd = epoll_create(OPEN_MAX);
if(efd == -1){
perr_exit("epoll create error");
}
tep.events = EPOLLIN;
tep.data.fd = listenfd;
res = epoll_ctl(efd, EPOLL_CTL_ADD, listenfd, &tep);//加入epoll树
if(res == -1){
perr_exit("epoll_ctr error");
}
while(1){
nready = epoll_wait(efd, ep, OPEN_MAX, -1);
if(nready == -1){
perr_exit("epoll_wait error");
}
for (i = 0; i < nready; i++)
{
if(!(ep[i].events & EPOLLIN)){
continue;
}
if (ep[i].data.fd == listenfd)
{
connfd = Accept(listenfd, (struct sockaddr*)&cliaddr, &clilen);
printf("client connect ip=%s, port=%d\n",inet_ntop(AF_INET, &cliaddr.sin_addr.s_addr, str, sizeof(str)),ntohs(cliaddr.sin_port));
tep.events = EPOLLIN;
tep.data.fd = connfd;
res = epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &tep);
if(res == -1){
perr_exit("epoll_ctr error");
}
}else{
n = Read(ep[i].data.fd, buf, sizeof(buf));
if(n == 0){
Close(ep[i].data.fd);
epoll_ctl(efd, EPOLL_CTL_DEL, ep[i].data.fd,NULL);
printf("client close\n");
}else if(n < 0){
perr_exit("Read error");
Close(ep[i].data.fd);
epoll_ctl(efd, EPOLL_CTL_DEL, ep[i].data.fd, NULL);
}else{
for (j = 0; j < n; j++)
{
buf[j] = toupper(buf[j]);
}
Write(ep[i].data.fd, buf, n);
}
}
}
}
Close(listenfd);
return 0;
}
非阻塞io边沿触发示例
read时不进行阻塞,epoll_wait当接收数据时才返回
/*
*epoll基于非阻塞I/O事件驱动
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define MAX_EVENTS 1024 //监听上限数
#define BUFLEN 4096
#define SERV_PORT 8080
void recvdata(int fd, int events, void *arg);
void senddata(int fd, int events, void *arg);
/* 描述就绪文件描述符相关信息 */
struct myevent_s {
int fd; //要监听的文件描述符
int events; //对应的监听事件
void *arg; //泛型参数
void (*call_back)(int fd, int events, void *arg); //回调函数
int status; //是否在监听:1->在红黑树上(监听), 0->不在(不监听)
char buf[BUFLEN];
int len;
long last_active; //记录每次加入红黑树 g_efd 的时间值
};
int g_efd; //全局变量, 保存epoll_create返回的文件描述符
struct myevent_s g_events[MAX_EVENTS+1]; //自定义结构体类型数组. +1-->listen fd
/*将结构体 myevent_s 成员变量 初始化*/
void eventset(struct myevent_s *ev, int fd, void (*call_back)(int, int, void *), void *arg)
{
ev->fd = fd;
ev->call_back = call_back;
ev->events = 0;
ev->arg = arg;
ev->status = 0;
//memset(ev->buf, 0, sizeof(ev->buf));
//ev->len = 0;
ev->last_active = time(NULL); //调用eventset函数的时间
return;
}
/* 向 epoll监听的红黑树 添加一个 文件描述符 */
void eventadd(int efd, int events, struct myevent_s *ev)
{
struct epoll_event epv = {0, {0}};
int op;
epv.data.ptr = ev;
epv.events = ev->events = events; //EPOLLIN 或 EPOLLOUT
if (ev->status == 1) { //已经在红黑树 g_efd 里
op = EPOLL_CTL_MOD; //修改其属性
} else { //不在红黑树里
op = EPOLL_CTL_ADD; //将其加入红黑树 g_efd, 并将status置1
ev->status = 1;
}
if (epoll_ctl(efd, op, ev->fd, &epv) < 0) //实际添加/修改
printf("event add failed [fd=%d], events[%d]\n", ev->fd, events);
else
printf("event add OK [fd=%d], op=%d, events[%0X]\n", ev->fd, op, events);
return ;
}
/* 从epoll 监听的 红黑树中删除一个 文件描述符*/
void eventdel(int efd, struct myevent_s *ev)
{
struct epoll_event epv = {0, {0}};
if (ev->status != 1) //不在红黑树上
return ;
epv.data.ptr = ev;
ev->status = 0; //修改状态
epoll_ctl(efd, EPOLL_CTL_DEL, ev->fd, &epv); //从红黑树 efd 上将 ev->fd 摘除
return ;
}
/* 当有文件描述符就绪, epoll返回, 调用该函数 与客户端建立链接 */
void acceptconn(int lfd, int events, void *arg)
{
struct sockaddr_in cin;
socklen_t len = sizeof(cin);
int cfd, i;
if ((cfd = accept(lfd, (struct sockaddr *)&cin, &len)) == -1) {
if (errno != EAGAIN && errno != EINTR) {
/* 暂时不做出错处理 */
}
printf("%s: accept, %s\n", __func__, strerror(errno));
return ;
}
do {
for (i = 0; i < MAX_EVENTS; i++) //从全局数组g_events中找一个空闲元素
if (g_events[i].status == 0) //类似于select中找值为-1的元素
break; //跳出 for
if (i == MAX_EVENTS) {
printf("%s: max connect limit[%d]\n", __func__, MAX_EVENTS);
break; //跳出do while(0) 不执行后续代码
}
int flag = 0;
if ((flag = fcntl(cfd, F_SETFL, O_NONBLOCK)) < 0) { //将cfd也设置为非阻塞
printf("%s: fcntl nonblocking failed, %s\n", __func__, strerror(errno));
break;
}
/* 给cfd设置一个 myevent_s 结构体, 回调函数 设置为 recvdata */
eventset(&g_events[i], cfd, recvdata, &g_events[i]);
eventadd(g_efd, EPOLLIN, &g_events[i]); //将cfd添加到红黑树g_efd中,监听读事件
} while(0);
printf("new connect [%s:%d][time:%ld], pos[%d]\n",
inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), g_events[i].last_active, i);
return ;
}
void recvdata(int fd, int events, void *arg)
{
struct myevent_s *ev = (struct myevent_s *)arg;
int len;
len = recv(fd, ev->buf, sizeof(ev->buf), 0); //读文件描述符, 数据存入myevent_s成员buf中
eventdel(g_efd, ev); //将该节点从红黑树上摘除
if (len > 0) {
ev->len = len;
ev->buf[len] = '\0'; //手动添加字符串结束标记
printf("C[%d]:%s\n", fd, ev->buf);
eventset(ev, fd, senddata, ev); //设置该 fd 对应的回调函数为 senddata
eventadd(g_efd, EPOLLOUT, ev); //将fd加入红黑树g_efd中,监听其写事件
} else if (len == 0) {
close(ev->fd);
/* ev-g_events 地址相减得到偏移元素位置 */
printf("[fd=%d] pos[%ld], closed\n", fd, ev-g_events);
} else {
close(ev->fd);
printf("recv[fd=%d] error[%d]:%s\n", fd, errno, strerror(errno));
}
return;
}
void senddata(int fd, int events, void *arg)
{
struct myevent_s *ev = (struct myevent_s *)arg;
int len;
len = send(fd, ev->buf, ev->len, 0); //直接将数据 回写给客户端。未作处理
/*
printf("fd=%d\tev->buf=%s\ttev->len=%d\n", fd, ev->buf, ev->len);
printf("send len = %d\n", len);
*/
if (len > 0) {
printf("send[fd=%d], [%d]%s\n", fd, len, ev->buf);
eventdel(g_efd, ev); //从红黑树g_efd中移除
eventset(ev, fd, recvdata, ev); //将该fd的 回调函数改为 recvdata
eventadd(g_efd, EPOLLIN, ev); //从新添加到红黑树上, 设为监听读事件
} else {
close(ev->fd); //关闭链接
eventdel(g_efd, ev); //从红黑树g_efd中移除
printf("send[fd=%d] error %s\n", fd, strerror(errno));
}
return ;
}
/*创建 socket, 初始化lfd */
void initlistensocket(int efd, short port)
{
int lfd = socket(AF_INET, SOCK_STREAM, 0);
fcntl(lfd, F_SETFL, O_NONBLOCK); //将socket设为非阻塞
/* void eventset(struct myevent_s *ev, int fd, void (*call_back)(int, int, void *), void *arg); */
eventset(&g_events[MAX_EVENTS], lfd, acceptconn, &g_events[MAX_EVENTS]);
/* void eventadd(int efd, int events, struct myevent_s *ev) */
eventadd(efd, EPOLLIN, &g_events[MAX_EVENTS]);
struct sockaddr_in sin;
memset(&sin, 0, sizeof(sin)); //bzero(&sin, sizeof(sin))
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = INADDR_ANY;
sin.sin_port = htons(port);
bind(lfd, (struct sockaddr *)&sin, sizeof(sin));
listen(lfd, 20);
return ;
}
int main(int argc, char *argv[])
{
unsigned short port = SERV_PORT;
if (argc == 2)
port = atoi(argv[1]); //使用用户指定端口.如未指定,用默认端口
g_efd = epoll_create(MAX_EVENTS+1); //创建红黑树,返回给全局 g_efd
if (g_efd <= 0)
printf("create efd in %s err %s\n", __func__, strerror(errno));
initlistensocket(g_efd, port); //初始化监听socket
struct epoll_event events[MAX_EVENTS+1]; //保存已经满足就绪事件的文件描述符数组
printf("server running:port[%d]\n", port);
int checkpos = 0, i;
while (1) {
/* 超时验证,每次测试100个链接,不测试listenfd 当客户端60秒内没有和服务器通信,则关闭此客户端链接 */
long now = time(NULL); //当前时间
for (i = 0; i < 100; i++, checkpos++) { //一次循环检测100个。 使用checkpos控制检测对象
if (checkpos == MAX_EVENTS)
checkpos = 0;
if (g_events[checkpos].status != 1) //不在红黑树 g_efd 上
continue;
long duration = now - g_events[checkpos].last_active; //客户端不活跃的世间
if (duration >= 60) {
close(g_events[checkpos].fd); //关闭与该客户端链接
printf("[fd=%d] timeout\n", g_events[checkpos].fd);
eventdel(g_efd, &g_events[checkpos]); //将该客户端 从红黑树 g_efd移除
}
}
/*监听红黑树g_efd, 将满足的事件的文件描述符加至events数组中, 1秒没有事件满足, 返回 0*/
int nfd = epoll_wait(g_efd, events, MAX_EVENTS+1, 1000);
if (nfd < 0) {
printf("epoll_wait error, exit\n");
break;
}
for (i = 0; i < nfd; i++) {
/*使用自定义结构体myevent_s类型指针, 接收 联合体data的void *ptr成员*/
struct myevent_s *ev = (struct myevent_s *)events[i].data.ptr;
if ((events[i].events & EPOLLIN) && (ev->events & EPOLLIN)) { //读就绪事件
ev->call_back(ev->fd, events[i].events, ev->arg);
}
if ((events[i].events & EPOLLOUT) && (ev->events & EPOLLOUT)) { //写就绪事件
ev->call_back(ev->fd, events[i].events, ev->arg);
}
}
}
/* 退出前释放所有资源 */
return 0;
}
thread_poll.h
#ifndef __THREADPOOL_H_
#define __THREADPOOL_H_
typedef struct threadpool_t threadpool_t;
/**
* @function threadpool_create
* @descCreates a threadpool_t object.
* @param thr_num thread num
* @param max_thr_num max thread size
* @param queue_max_size size of the queue.
* @return a newly created thread pool or NULL
*/
threadpool_t *threadpool_create(int min_thr_num, int max_thr_num, int queue_max_size);
/**
* @function threadpool_add
* @desc add a new task in the queue of a thread pool
* @param pool Thread pool to which add the task.
* @param function Pointer to the function that will perform the task.
* @param argument Argument to be passed to the function.
* @return 0 if all goes well,else -1
*/
int threadpool_add(threadpool_t *pool, void*(*function)(void *arg), void *arg);
/**
* @function threadpool_destroy
* @desc Stops and destroys a thread pool.
* @param pool Thread pool to destroy.
* @return 0 if destory success else -1
*/
int threadpool_destroy(threadpool_t *pool);
/**
* @desc get the thread num
* @pool pool threadpool
* @return # of the thread
*/
int threadpool_all_threadnum(threadpool_t *pool);
/**
* desc get the busy thread num
* @param pool threadpool
* return # of the busy thread
*/
int threadpool_busy_threadnum(threadpool_t *pool);
#endif
pthread_poll.c
#include
#include
#include
#include
#include
#include
#include
#include
#include "threadpool.h"
#define DEFAULT_TIME 10 /*10s检测一次*/
#define MIN_WAIT_TASK_NUM 10 /*如果queue_size > MIN_WAIT_TASK_NUM 添加新的线程到线程池*/
#define DEFAULT_THREAD_VARY 10 /*每次创建和销毁线程的个数*/
#define true 1
#define false 0
typedef struct {
void *(*function)(void *); /* 函数指针,回调函数 */
void *arg; /* 上面函数的参数 */
} threadpool_task_t; /* 各子线程任务结构体 */
/* 描述线程池相关信息 */
struct threadpool_t {
pthread_mutex_t lock; /* 用于锁住本结构体 */
pthread_mutex_t thread_counter; /* 记录忙状态线程个数de琐 -- busy_thr_num */
pthread_cond_t queue_not_full; /* 当任务队列满时,添加任务的线程阻塞,等待此条件变量 */
pthread_cond_t queue_not_empty; /* 任务队列里不为空时,通知等待任务的线程 */
pthread_t *threads; /* 存放线程池中每个线程的tid。数组 */
pthread_t adjust_tid; /* 存管理线程tid */
threadpool_task_t *task_queue; /* 任务队列 */
int min_thr_num; /* 线程池最小线程数 */
int max_thr_num; /* 线程池最大线程数 */
int live_thr_num; /* 当前存活线程个数 */
int busy_thr_num; /* 忙状态线程个数 */
int wait_exit_thr_num; /* 要销毁的线程个数 */
int queue_front; /* task_queue队头下标 */
int queue_rear; /* task_queue队尾下标 */
int queue_size; /* task_queue队中实际任务数 */
int queue_max_size; /* task_queue队列可容纳任务数上限 */
int shutdown; /* 标志位,线程池使用状态,true或false */
};
/**
* @function void *threadpool_thread(void *threadpool)
* @desc the worker thread
* @param threadpool the pool which own the thread
*/
void *threadpool_thread(void *threadpool);
/**
* @function void *adjust_thread(void *threadpool);
* @desc manager thread
* @param threadpool the threadpool
*/
void *adjust_thread(void *threadpool);
/**
* check a thread is alive
*/
int is_thread_alive(pthread_t tid);
int threadpool_free(threadpool_t *pool);
threadpool_t *threadpool_create(int min_thr_num, int max_thr_num, int queue_max_size)
{
int i;
threadpool_t *pool = NULL;
do {
if((pool = (threadpool_t *)malloc(sizeof(threadpool_t))) == NULL) {
printf("malloc threadpool fail");
break;/*跳出do while*/
}
pool->min_thr_num = min_thr_num;
pool->max_thr_num = max_thr_num;
pool->busy_thr_num = 0;
pool->live_thr_num = min_thr_num; /* 活着的线程数 初值=最小线程数 */
pool->queue_size = 0; /* 有0个产品 */
pool->queue_max_size = queue_max_size;
pool->queue_front = 0;
pool->queue_rear = 0;
pool->shutdown = false; /* 不关闭线程池 */
/* 根据最大线程上限数, 给工作线程数组开辟空间, 并清零 */
pool->threads = (pthread_t *)malloc(sizeof(pthread_t)*max_thr_num);
if (pool->threads == NULL) {
printf("malloc threads fail");
break;
}
memset(pool->threads, 0, sizeof(pthread_t)*max_thr_num);
/* 队列开辟空间 */
pool->task_queue = (threadpool_task_t *)malloc(sizeof(threadpool_task_t)*queue_max_size);
if (pool->task_queue == NULL) {
printf("malloc task_queue fail");
break;
}
/* 初始化互斥琐、条件变量 */
if (pthread_mutex_init(&(pool->lock), NULL) != 0
|| pthread_mutex_init(&(pool->thread_counter), NULL) != 0
|| pthread_cond_init(&(pool->queue_not_empty), NULL) != 0
|| pthread_cond_init(&(pool->queue_not_full), NULL) != 0)
{
printf("init the lock or cond fail");
break;
}
/* 启动 min_thr_num 个 work thread */
for (i = 0; i < min_thr_num; i++) {
pthread_create(&(pool->threads[i]), NULL, threadpool_thread, (void *)pool);/*pool指向当前线程池*/
printf("start thread 0x%x...\n", (unsigned int)pool->threads[i]);
}
pthread_create(&(pool->adjust_tid), NULL, adjust_thread, (void *)pool);/* 启动管理者线程 */
return pool;
} while (0);
threadpool_free(pool); /* 前面代码调用失败时,释放poll存储空间 */
return NULL;
}
/* 向线程池中 添加一个任务 */
int threadpool_add(threadpool_t *pool, void*(*function)(void *arg), void *arg)
{
pthread_mutex_lock(&(pool->lock));
/* ==为真,队列已经满, 调wait阻塞 */
while ((pool->queue_size == pool->queue_max_size) && (!pool->shutdown)) {
pthread_cond_wait(&(pool->queue_not_full), &(pool->lock));
}
if (pool->shutdown) {
pthread_mutex_unlock(&(pool->lock));
}
/* 清空 工作线程 调用的回调函数 的参数arg */
if (pool->task_queue[pool->queue_rear].arg != NULL) {
free(pool->task_queue[pool->queue_rear].arg);
pool->task_queue[pool->queue_rear].arg = NULL;
}
/*添加任务到任务队列里*/
pool->task_queue[pool->queue_rear].function = function;
pool->task_queue[pool->queue_rear].arg = arg;
pool->queue_rear = (pool->queue_rear + 1) % pool->queue_max_size; /* 队尾指针移动, 模拟环形 */
pool->queue_size++;
/*添加完任务后,队列不为空,唤醒线程池中 等待处理任务的线程*/
pthread_cond_signal(&(pool->queue_not_empty));
pthread_mutex_unlock(&(pool->lock));
return 0;
}
/* 线程池中各个工作线程 */
void *threadpool_thread(void *threadpool)
{
threadpool_t *pool = (threadpool_t *)threadpool;
threadpool_task_t task;
while (true) {
/* Lock must be taken to wait on conditional variable */
/*刚创建出线程,等待任务队列里有任务,否则阻塞等待任务队列里有任务后再唤醒接收任务*/
pthread_mutex_lock(&(pool->lock));
/*queue_size == 0 说明没有任务,调 wait 阻塞在条件变量上, 若有任务,跳过该while*/
while ((pool->queue_size == 0) && (!pool->shutdown)) {
printf("thread 0x%x is waiting\n", (unsigned int)pthread_self());
pthread_cond_wait(&(pool->queue_not_empty), &(pool->lock));
/*清除指定数目的空闲线程,如果要结束的线程个数大于0,结束线程*/
if (pool->wait_exit_thr_num > 0) {
pool->wait_exit_thr_num--;
/*如果线程池里线程个数大于最小值时可以结束当前线程*/
if (pool->live_thr_num > pool->min_thr_num) {
printf("thread 0x%x is exiting\n", (unsigned int)pthread_self());
pool->live_thr_num--;
pthread_mutex_unlock(&(pool->lock));
pthread_exit(NULL);
}
}
}
/*如果指定了true,要关闭线程池里的每个线程,自行退出处理*/
if (pool->shutdown) {
pthread_mutex_unlock(&(pool->lock));
printf("thread 0x%x is exiting\n", (unsigned int)pthread_self());
pthread_exit(NULL); /* 线程自行结束 */
}
/*从任务队列里获取任务, 是一个出队操作*/
task.function = pool->task_queue[pool->queue_front].function;
task.arg = pool->task_queue[pool->queue_front].arg;
pool->queue_front = (pool->queue_front + 1) % pool->queue_max_size; /* 出队,模拟环形队列 */
pool->queue_size--;
/*通知可以有新的任务添加进来*/
pthread_cond_broadcast(&(pool->queue_not_full));
/*任务取出后,立即将 线程池琐 释放*/
pthread_mutex_unlock(&(pool->lock));
/*执行任务*/
printf("thread 0x%x start working\n", (unsigned int)pthread_self());
pthread_mutex_lock(&(pool->thread_counter)); /*忙状态线程数变量琐*/
pool->busy_thr_num++; /*忙状态线程数+1*/
pthread_mutex_unlock(&(pool->thread_counter));
(*(task.function))(task.arg); /*执行回调函数任务*/
//task.function(task.arg); /*执行回调函数任务*/
/*任务结束处理*/
printf("thread 0x%x end working\n", (unsigned int)pthread_self());
pthread_mutex_lock(&(pool->thread_counter));
pool->busy_thr_num--; /*处理掉一个任务,忙状态数线程数-1*/
pthread_mutex_unlock(&(pool->thread_counter));
}
pthread_exit(NULL);
}
/* 管理线程 */
void *adjust_thread(void *threadpool)
{
int i;
threadpool_t *pool = (threadpool_t *)threadpool;
while (!pool->shutdown) {
sleep(DEFAULT_TIME); /*定时 对线程池管理*/
pthread_mutex_lock(&(pool->lock));
int queue_size = pool->queue_size; /* 关注 任务数 */
int live_thr_num = pool->live_thr_num; /* 存活 线程数 */
pthread_mutex_unlock(&(pool->lock));
pthread_mutex_lock(&(pool->thread_counter));
int busy_thr_num = pool->busy_thr_num; /* 忙着的线程数 */
pthread_mutex_unlock(&(pool->thread_counter));
/* 创建新线程 算法: 任务数大于最小线程池个数, 且存活的线程数少于最大线程个数时 如:30>=10 && 40<100*/
if (queue_size >= MIN_WAIT_TASK_NUM && live_thr_num < pool->max_thr_num) {
pthread_mutex_lock(&(pool->lock));
int add = 0;
/*一次增加 DEFAULT_THREAD 个线程*/
for (i = 0; i < pool->max_thr_num && add < DEFAULT_THREAD_VARY
&& pool->live_thr_num < pool->max_thr_num; i++) {
if (pool->threads[i] == 0 || !is_thread_alive(pool->threads[i])) {
pthread_create(&(pool->threads[i]), NULL, threadpool_thread, (void *)pool);
add++;
pool->live_thr_num++;
}
}
pthread_mutex_unlock(&(pool->lock));
}
/* 销毁多余的空闲线程 算法:忙线程X2 小于 存活的线程数 且 存活的线程数 大于 最小线程数时*/
if ((busy_thr_num * 2) < live_thr_num && live_thr_num > pool->min_thr_num) {
/* 一次销毁DEFAULT_THREAD个线程, 隨機10個即可 */
pthread_mutex_lock(&(pool->lock));
pool->wait_exit_thr_num = DEFAULT_THREAD_VARY; /* 要销毁的线程数 设置为10 */
pthread_mutex_unlock(&(pool->lock));
for (i = 0; i < DEFAULT_THREAD_VARY; i++) {
/* 通知处在空闲状态的线程, 他们会自行终止*/
pthread_cond_signal(&(pool->queue_not_empty));
}
}
}
return NULL;
}
int threadpool_destroy(threadpool_t *pool)
{
int i;
if (pool == NULL) {
return -1;
}
pool->shutdown = true;
/*先销毁管理线程*/
pthread_join(pool->adjust_tid, NULL);
for (i = 0; i < pool->live_thr_num; i++) {
/*通知所有的空闲线程*/
pthread_cond_broadcast(&(pool->queue_not_empty));
}
for (i = 0; i < pool->live_thr_num; i++) {
pthread_join(pool->threads[i], NULL);
}
threadpool_free(pool);
return 0;
}
int threadpool_free(threadpool_t *pool)
{
if (pool == NULL) {
return -1;
}
if (pool->task_queue) {
free(pool->task_queue);
}
if (pool->threads) {
free(pool->threads);
pthread_mutex_lock(&(pool->lock));
pthread_mutex_destroy(&(pool->lock));
pthread_mutex_lock(&(pool->thread_counter));
pthread_mutex_destroy(&(pool->thread_counter));
pthread_cond_destroy(&(pool->queue_not_empty));
pthread_cond_destroy(&(pool->queue_not_full));
}
free(pool);
pool = NULL;
return 0;
}
int threadpool_all_threadnum(threadpool_t *pool)
{
int all_threadnum = -1;
pthread_mutex_lock(&(pool->lock));
all_threadnum = pool->live_thr_num;
pthread_mutex_unlock(&(pool->lock));
return all_threadnum;
}
int threadpool_busy_threadnum(threadpool_t *pool)
{
int busy_threadnum = -1;
pthread_mutex_lock(&(pool->thread_counter));
busy_threadnum = pool->busy_thr_num;
pthread_mutex_unlock(&(pool->thread_counter));
return busy_threadnum;
}
int is_thread_alive(pthread_t tid)
{
int kill_rc = pthread_kill(tid, 0); //发0号信号,测试线程是否存活
if (kill_rc == ESRCH) {
return false;
}
return true;
}
/*测试*/
#if 1
/* 线程池中的线程,模拟处理业务 */
void *process(void *arg)
{
printf("thread 0x%x working on task %d\n ",(unsigned int)pthread_self(),*(int *)arg);
sleep(1);
printf("task %d is end\n",*(int *)arg);
return NULL;
}
int main(void)
{
/*threadpool_t *threadpool_create(int min_thr_num, int max_thr_num, int queue_max_size);*/
threadpool_t *thp = threadpool_create(3,100,100);/*创建线程池,池里最小3个线程,最大100,队列最大100*/
printf("pool inited");
//int *num = (int *)malloc(sizeof(int)*20);
int num[20], i;
for (i = 0; i < 20; i++) {
num[i]=i;
printf("add task %d\n",i);
threadpool_add(thp, process, (void*)&num[i]); /* 向线程池中添加任务 */
}
sleep(10); /* 等子线程完成任务 */
threadpool_destroy(thp);
return 0;
}
#endif
server
#include
#include
#include
#include
#include
#include
#include
#define MAXLINE 80
#define SERV_PORT 6666
int main(void)
{
struct sockaddr_in servaddr, cliaddr;
socklen_t cliaddr_len;
int sockfd;
char buf[MAXLINE];
char str[INET_ADDRSTRLEN];
int i, n;
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
bind(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
printf("Accepting connections ...\n");
while (1) {
cliaddr_len = sizeof(cliaddr);
n = recvfrom(sockfd, buf, MAXLINE,0, (struct sockaddr *)&cliaddr, &cliaddr_len);
if (n == -1)
perror("recvfrom error");
printf("received from %s at PORT %d\n",
inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
ntohs(cliaddr.sin_port));
for (i = 0; i < n; i++)
buf[i] = toupper(buf[i]);
n = sendto(sockfd, buf, n, 0, (struct sockaddr *)&cliaddr, sizeof(cliaddr));
if (n == -1)
perror("sendto error");
}
close(sockfd);
return 0;
}
client
#include
#include
#include
#include
#include
#include
#include
#define MAXLINE 80
#define SERV_PORT 6666
int main(int argc, char *argv[])
{
struct sockaddr_in servaddr;
int sockfd, n;
char buf[MAXLINE];
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
servaddr.sin_port = htons(SERV_PORT);
while (fgets(buf, MAXLINE, stdin) != NULL) {
n = sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr *)&servaddr, sizeof(servaddr));
if (n == -1)
perror("sendto error");
n = recvfrom(sockfd, buf, MAXLINE, 0, NULL, 0);
if (n == -1)
perror("recvfrom error");
write(STDOUT_FILENO, buf, n);
}
close(sockfd);
return 0;
}
IP:172.30.10.255(广播地址)
IP:172.30.10.1(网关)
使用setsockopt设置权限
server
#include
#include
#include
#include
#include
#include
#include
#define SERVER_PORT 8000 /* 无关紧要 */
#define MAXLINE 1500
#define BROADCAST_IP "192.168.42.255"
#define CLIENT_PORT 9000 /*客户端端口号 重要 */
int main(void)
{
int sockfd;
struct sockaddr_in serveraddr, clientaddr;
char buf[MAXLINE];
/* 构造用于UDP通信的套接字 */
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
bzero(&serveraddr, sizeof(serveraddr));
serveraddr.sin_family = AF_INET; /* IPv4 */
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY); /* 本地任意IP INADDR_ANY = 0 */
serveraddr.sin_port = htons(SERVER_PORT);
bind(sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));
int flag = 1;
setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &flag, sizeof(flag));
/*构造 client 地址 IP+端口 192.168.7.255+9000 */
bzero(&clientaddr, sizeof(clientaddr));
clientaddr.sin_family = AF_INET;
inet_pton(AF_INET, BROADCAST_IP, &clientaddr.sin_addr.s_addr);
clientaddr.sin_port = htons(CLIENT_PORT);
int i = 0;
while (1) {
sprintf(buf, "Drink %d glasses of water\n", i++);
//fgets(buf, sizeof(buf), stdin);
sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr *)&clientaddr, sizeof(clientaddr));
sleep(1);
}
close(sockfd);
return 0;
}
client
#include
#include
#include
#include
#include
#define SERVER_PORT 8000
#define MAXLINE 4096
#define CLIENT_PORT 9000
int main(int argc, char *argv[])
{
struct sockaddr_in localaddr;
int confd;
ssize_t len;
char buf[MAXLINE];
//1.创建一个socket
confd = socket(AF_INET, SOCK_DGRAM, 0);
//2.初始化本地端地址
bzero(&localaddr, sizeof(localaddr));
localaddr.sin_family = AF_INET;
inet_pton(AF_INET, "0.0.0.0" , &localaddr.sin_addr.s_addr);//0.0.0.0相当于INADDR_ANY
localaddr.sin_port = htons(CLIENT_PORT);
int ret = bind(confd, (struct sockaddr *)&localaddr, sizeof(localaddr)); //显示绑定不能省略
if (ret == 0)
printf("...bind ok...\n");
while (1) {
len = recvfrom(confd, buf, sizeof(buf), 0, NULL, 0);
write(STDOUT_FILENO, buf, len);
}
close(confd);
return 0;
}
server
#include
#include
#include
#include
#include
#define SERVER_PORT 8000
#define CLIENT_PORT 9000
#define MAXLINE 1500
#define GROUP "239.0.0.2"
int main(void)
{
int sockfd;
struct sockaddr_in serveraddr, clientaddr;
char buf[MAXLINE] = "itcast\n";
struct ip_mreqn group;
sockfd = socket(AF_INET, SOCK_DGRAM, 0); /* 构造用于UDP通信的套接字 */
bzero(&serveraddr, sizeof(serveraddr));
serveraddr.sin_family = AF_INET; /* IPv4 */
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY); /* 本地任意IP INADDR_ANY = 0 */
serveraddr.sin_port = htons(SERVER_PORT);
bind(sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));
inet_pton(AF_INET, GROUP, &group.imr_multiaddr); /* 设置组地址 */
inet_pton(AF_INET, "0.0.0.0", &group.imr_address); /* 本地任意IP */
group.imr_ifindex = if_nametoindex("eth0"); /* 给出网卡名,转换为对应编号: eth0 --> 编号 命令:ip ad */
setsockopt(sockfd, IPPROTO_IP, IP_MULTICAST_IF, &group, sizeof(group)); /* 组播权限 */
bzero(&clientaddr, sizeof(clientaddr)); /* 构造 client 地址 IP+端口 */
clientaddr.sin_family = AF_INET;
inet_pton(AF_INET, GROUP, &clientaddr.sin_addr.s_addr); /* IPv4 239.0.0.2+9000 */
clientaddr.sin_port = htons(CLIENT_PORT);
int i = 0;
while (1) {
sprintf(buf, "itcast %d\n", i++);
//fgets(buf, sizeof(buf), stdin);
sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr *)&clientaddr, sizeof(clientaddr));
sleep(1);
}
close(sockfd);
return 0;
}
client
#include
#include
#include
#include
#include
#define SERVER_PORT 8000
#define CLIENT_PORT 9000
#define GROUP "239.0.0.2"
int main(int argc, char *argv[])
{
struct sockaddr_in localaddr;
int confd;
ssize_t len;
char buf[BUFSIZ];
struct ip_mreqn group; /* 组播结构体 */
confd = socket(AF_INET, SOCK_DGRAM, 0);
bzero(&localaddr, sizeof(localaddr)); /* 初始化 */
localaddr.sin_family = AF_INET;
inet_pton(AF_INET, "0.0.0.0" , &localaddr.sin_addr.s_addr);
localaddr.sin_port = htons(CLIENT_PORT);
bind(confd, (struct sockaddr *)&localaddr, sizeof(localaddr));
inet_pton(AF_INET, GROUP, &group.imr_multiaddr); /* 设置组地址 */
inet_pton(AF_INET, "0.0.0.0", &group.imr_address); /* 使用本地任意IP添加到组播组 */
group.imr_ifindex = if_nametoindex("eth0"); /* 通过网卡名-->编号 ip ad */
setsockopt(confd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, sizeof(group));/* 设置client 加入多播组 */
while (1) {
len = recvfrom(confd, buf, sizeof(buf), 0, NULL, 0);
write(STDOUT_FILENO, buf, len);
}
close(confd);
return 0;
}
server
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "wrap.h"
#define SERV_ADDR "serv.socket"
int main(void)
{
int lfd, cfd, len, size, i;
struct sockaddr_un servaddr, cliaddr;
char buf[4096];
lfd = Socket(AF_UNIX, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sun_family = AF_UNIX;
strcpy(servaddr.sun_path,SERV_ADDR);
len = offsetof(struct sockaddr_un, sun_path) + strlen(servaddr.sun_path); /* servaddr total len */
unlink(SERV_ADDR); /* 确保bind之前serv.sock文件不存在,bind会创建该文件 */
Bind(lfd, (struct sockaddr *)&servaddr, len); /* 参3不能是sizeof(servaddr) */
Listen(lfd, 20);
printf("Accept ...\n");
while (1) {
len = sizeof(cliaddr);
cfd = Accept(lfd, (struct sockaddr *)&cliaddr, (socklen_t *)&len);
len -= offsetof(struct sockaddr_un, sun_path); /* 得到文件名的长度 */
cliaddr.sun_path[len] = '\0'; /* 确保打印时,没有乱码出现 */
printf("client bind filename %s\n", cliaddr.sun_path);
while ((size = read(cfd, buf, sizeof(buf))) > 0) {
for (i = 0; i < size; i++)
buf[i] = toupper(buf[i]);
write(cfd, buf, size);
}
close(cfd);
}
close(lfd);
return 0;
}
client
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "wrap.h"
#define SERV_ADDR "serv.socket"
#define CLIE_ADDR "clie.socket"
int main(void)
{
int cfd, len;
struct sockaddr_un servaddr, cliaddr;
char buf[4096];
cfd = Socket(AF_UNIX, SOCK_STREAM, 0);
bzero(&cliaddr, sizeof(cliaddr));
cliaddr.sun_family = AF_UNIX;
strcpy(cliaddr.sun_path,CLIE_ADDR);
len = offsetof(struct sockaddr_un, sun_path) + strlen(cliaddr.sun_path); /* 计算客户端地址结构有效长度 */
unlink(CLIE_ADDR);
Bind(cfd, (struct sockaddr *)&cliaddr, len); /* 客户端也需要bind, 不能依赖自动绑定*/
bzero(&servaddr, sizeof(servaddr)); /* 构造server 地址 */
servaddr.sun_family = AF_UNIX;
strcpy(servaddr.sun_path,SERV_ADDR);
len = offsetof(struct sockaddr_un, sun_path) + strlen(servaddr.sun_path); /* 计算服务器端地址结构有效长度 */
Connect(cfd, (struct sockaddr *)&servaddr, len);
while (fgets(buf, sizeof(buf), stdin) != NULL) {
write(cfd, buf, strlen(buf));
len = read(cfd, buf, sizeof(buf));
write(STDOUT_FILENO, buf, len);
}
close(cfd);
return 0;
}