并发网络服务器 + 网络编程
并发网络服务器:
- 基本概念解释
多进程并发服务器
togglesp.c
void sigchld_handler(int sig) {
while (waitpid(-1, 0, WNOHANG) > 0);
return;
}
int main(int argc, char **argv) {
int listen_sock, conn_sock, port;
socklen_t clientlen=sizeof(struct sockaddr_in);
struct sockaddr_in clientaddr;
if (argc != 2) { … }
port = atoi(argv[1]);
signal(SIGCHLD, sigchld_handler);
listen_sock = open_listen_sock(port);
while (1) {
conn_sock = accept(listen_sock, (SA *) &clientaddr, &clientlen);
if (fork() == 0) {
close(listen_sock);
toggle(conn_sock); /* Child process services client */
close(conn_sock);
exit(0);
}
close(conn_sock);
}
}
特点:父子共享打开文件表,但不共享用户地址空间。
优缺点
优点:每个进程都有独立的地址空间,进程间不会相互影响,有较好的可靠性和安全性
缺点:进程间共享状态信息变得麻烦,IPC机制开销很高,进程间数据共享低效
基于多线程并发服务器
togglest.c:
int main(int argc, char **argv) {
int listen_sock, *conn_sock_p, port;
socklen_t clientlen=sizeof(struct sockaddr_in);
struct sockaddr_in clientaddr;
pthread_t tid;
if (argc != 2) {。。。 }
port = atoi(argv[1]);
listen_sock = open_listen_sock(port);
while (1) {
conn_sock_p = malloc(sizeof(int));
*conn_sock_p = accept(listen_sock, (SA *) &clientaddr, &clientlen);
pthread_create(&tid, NULL, serve_client, conn_sock_p);
}
}
void * serve_client (void *vargp) {
int conn_sock = *((int *)vargp);
pthread_detach(pthread_self());
free(vargp);
toggle(conn_sock);
close(conn_sock);
return NULL;
}
预线程化并发服务器
-
基本思想
预先创建一批工作者线程,每次建立一个连接,工作者线程就领取一个任务,负责与一个客户端通信以消除服务器运行过程中创建、撤销线程的开销.
image.png
任务池定义(task_pool.c、task_pool.h)
生产者/消费者模型
# inpos、outpos分别是缓冲区写入、读出指针
# mutex:为互斥信号量
# avail、ready是同步信号量
typedef struct {
int *socks; /* Buffer array */
int cnt; /* Maximum number of cell */
int inpos; /* buf[inpos] is first available cell */
int outpos; /* buf[outpos] is fist item */
sem_t mutex; /* Protects accesses to socks */
sem_t avail; /* Counts available cells */
sem_t ready; /* Counts ready items */
} task_pool_t;
缓冲区初始化
void task_pool_init(task_pool_t *tp, int n)
{
tp->socks = Calloc(n, sizeof(int));
tp->cnt = n; /* socks holds max of n items */
tp->inpos= tp->outpos = 0; /* Empty socks iff inpos== outpos */
sem_init(&tp->mutex, 0, 1); /* Binary semaphore for locking */
sem_init(&tp->avail, 0, cnt); /* Initially, socks has cnt empty cell */
sem_init(&tp->ready, 0, 0); /* Initially, socks has zero data items */
}
读写缓冲区
void task_insert (task_pool_t *tp, int item){
sem_wait(&tp->avail); /* Wait for available cell */
sem_wait(&tp->mutex); /* Lock the shared variable tail pointer */
tp->socks[tp->inpos] = item; /* Insert the item */
tp-> inpos =(tp-> inpos +1)%(tp->cnt); /* adjuset tail point */
sem_post(&tp->mutex); /* Unlock the buffer */
sem_post(&tp->ready); /* Announce available item */
}
int task_remove(task_pool_t *tp){。。。}
预线程化服务器代码(togglest_pre.c)
int main(int argc, char **argv) {
int i, listen_sock, conn_sock, port;
socklen_t clientlen=sizeof(struct sockaddr_in);
struct sockaddr_in clientaddr;
pthread_t tid;
if (argc != 2) { 。。。 }
port = atoi(argv[1]);
task_pool_init(&tp, SBUFSIZE);
listen_sock = open_listen_sock(port);
for (i = 0; i < NTHREADS; i++) /* Create worker threads */
pthread_create(&tid, NULL, serve_client, NULL);
while (1) {
conn_sock = accept(listen_sock, (SA *) &clientaddr, &clientlen);
task_insert(&tp, conn_sock); /* Insert conn_sock in task pool */
}
}
void * serve_client(void *vargp) {
pthread_detach(pthread_self());
while (1) {
int conn_sock = task_remove(&tp);
toggle(conn_sock);
close(conn_sock);
}
}
网络编程:
套接字、
套接字编程模型
结构:网卡、TCP协议、套接字(Socket)
套接字:含有进程接收信息的完整地址(Socket地址:IP地址、端口号)
- Internet连接客户端与服务器的网卡,
- TCP/IP协议软件连接Socket与网卡,
- 套接字接口连接进程与Socket,
TCP连接整合了以上两个工具,tcp连接是连接通讯双方套接字的一条通信线路.一条TCP连接实际上就是一个文件描述符,可用read/write或send/recv进行数据收发.
-
TCP连接实例
服务器端口号:规定为80
客户端端口号:随机分配,12345
image.png
字节序、
-
网络序(网络序):高位在低地址字节
大端模式 -
主机序(小端模式):低位在低地址字节
小端模式 -
主机序与网络序的转换
unsigned long int htonl(unsigned long int hostlong);
unsigned short int htons(unsigned short int hostshort);
返回:网络序的值。
unsigned long int ntohl(unsigned long int netlong);
unsigned short int ntohs(unsiged short int netshort);
返回:主机序的值。 -
IP地址
由32位整数网络序0x8002c2f2转换成点分十进制128.2.193.242
128=0x80,2=0x02,193=0xc2,242=0xf2
转换函数:
int inet_aton(const char *cp, struct in_addr *inp);
char *inet_ntoa(struct in_addr in);
a: 字符串 n:32位整数
网络通信API函数:
编程框架(一)客户端
(1)创建套接字
int socket(int domain, int type , int protocol);
示例:client_sock = socket(AF_INET , SOCK_STREAM, 0);
(2) connect 函数
int connect (int client_sock , struct sockaddr *serv_addr , int addrlen);
(3)包装函数open_client_sock
(二)服务器端
(1)创建
int socket(int domain, int type , int protocol);
(2)绑定
int bind(int serv_sock, struct sockaddr *my_addr , int addrlen);
(3)监听
int listen(int serv_sock, int backlog);
(4)接受连接请求
int accept(int listen_sock, struct sockaddr *addr , int *addrlen);
包装函数:open_listen_sock