UNP学习笔记二--简单的并发服务器(concurrent servers)
1、一个典型的tcp client/server调用的函数顺序如下:
2、tcp server会在accept处等待客户端连接;而udp server则会在recvfrom函数处等待客户端连接。
3、一个简单的并发服务器使用fork和exec来完成客户端的并发请求处理。fork以后,父进程关闭connect fd,子进程关闭listen fd,子进程在完成逻辑处理后关闭 connent fd。
#include "unp.h"
int main(int argc, char **argv)
{
int listenfd, connfd;
pid_t childpid;
socklen_t clilen;
struct sockaddr_in cliaddr, servaddr;
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, (SA *) &servaddr, sizeof(servaddr));
Listen(listenfd, LISTENQ);
for ( ; ; ) {
clilen = sizeof(cliaddr);
connfd = Accept(listenfd, (SA *) &cliaddr, &clilen);
if ( (childpid = Fork()) == 0) { /* child process */
Close(listenfd); /* close listening socket */
str_echo(connfd); /* process the request */
exit(0);
}
Close(connfd); /* parent closes connected socket */
}
}
以下小结来自:http://blog.chinaunix.net/u/21000/showart.php?id=143807
面向连接的编程模式——TCP套接字
服务器端编程模板:
socket(); //创建套接字,同时也是一个文件描述符
bind(); //邦定套接字和服务器端口(包括IP地址和端口号),邦定之前要先初始化IP地址和端口
listen(); //监听该端口
accept(); //一般在一个while(1)循环体里。如果有连接请求则接受请求,返回一个新的与客户端绑定了的套接字(称为连接套接字,与前面的监听套接字相对),并产生一个新的进程或线程处理该客户的请求
recv() or send();//与客户端交换数据,或处理程序,一般在新产生的的进程或线程里的一个循环体里
close(); //别忘了关闭套接字,当然,如果中途发生错误,退出之前也应该关闭套接字
客户端模版:
socket();
connect(); //向客户端发送连接请求,客户端是不用邦定的,由系统自动为套接字分配端口,该函数把本地IP和该线程的端口发送给服务器
send() or recv(); //与服务器交换数据
close(); //同上
面向无连接的编程模式——UDP套接字
服务器端编程模版:
socket();
bind(); //绑定监听端口
recvfrom(); //堵塞,直到接收到数据报
sendto(); //处理客户数据或作出回应
close();
客户端编程模版:
socket();
sendto();
recvfrom(); //堵塞,直到接收到回应,还要判断是不是特定服务器的回应,一般是在一个while循环里。
close();
对比TCP和UDP,前者需要一个特定的端口作为监听端口,专门接收来自客户端的服务请求。send()中不用包含目标IP地址和端口,因为客户和服务器已经建立了连接,直接对连接套接口操作就可以了,而recv()也不用存放信息的IP地址和端口。而后者不需要监听端口,只需要一个端口接收数据报就可以了,但sendto()需要目标IP地址和端口,而recv()也需要存放IP和端口的空间。
另外,应用UDP的客户端还需要判断接收到的数据是不是特定服务器的回复,这需要比较IP地址和端口。
并发的实现方法
应用多进程:
当accept()接收到新的连接请求,应用fork()系统调用,产生子进程处理客户请求。
fork()函数原型:
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void)
返回值:子进程为0,父进程为子进程的ID,出错为-1
子进程是父进程的副本,子进程中有父进程的数据空间、堆栈的一份copy,注意是copy而不是共享,这是多进程与多线程最主要的区别,也导致截然不同的处理方式。
一般可以根据fork()的返回值来使父、子进程进行不同的处理,如:
while (1){
if((connectSoc = accept(listenSoc,(struct sockaddr *)&client, &sin_size)) == -1){
perror("accept() error!\n");
close(listenSoc);
exit(1);
}
pid = fork();
if(pid > 0){ //父进程处理程序,继续接收新的客户请求
close(connectSoc);
continue;
}
else if (pid == 0){//子进程处理程序
close(listenSoc);
printf("You got a connection from %s\n",inet_ntoa(client.sin_addr));
if(send(connectSoc,Msg,25,0) == -1){
perror("send() error!\n");
}
close(connectSoc);
exit(1);
}
else {
printf("fork() error!\n");
close(listenSoc);
exit(1);
}
}