The Socket API, Part 3: Concurrent Servers
转:http://www.linuxforu.com/2011/10/socket-api-part-3-concurrent-servers/
By Pankaj Tanwar on October 1, 2011 in Coding, Developers · 2 Comments
Welcome to another dose of socket programming! Till now, we’ve created servers that are capable of creating connections with multiple clients [Part 1 & Part 2], but the problem is that the server will communicate with only one client at any point in time. This is because there is only one socket descriptor, cfd
, created to communicate with a client — and all connections will wait on the same descriptor. Now, let’s use the fork()
system call to fork a copy of the server for each client.
Here is the code included from the previous article. This time it is for IPv4…
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
|
#include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <signal.h> int main() { int sfd, cfd; socklen_t len; char ch, buff[INET_ADDRSTRLEN]; struct sockaddr_in saddr, caddr; sfd= socket(AF_INET, SOCK_STREAM, 0); saddr.sin_family=AF_INET; saddr.sin_addr.s_addr=htonl(INADDR_ANY); saddr.sin_port=htons(1205); bind(sfd, ( struct sockaddr *)&saddr, sizeof (saddr)); listen(sfd, 5); signal (SIGCHLD, SIG_IGN); while (1) { printf ( "Server waiting\n" ); len= sizeof (caddr); cfd=accept(sfd, ( struct sockaddr *)&caddr, &len); if ( fork() == 0) { printf ( "Child Server Created Handling connection with %s\n" , inet_ntop(AF_INET, &caddr.sin_addr, buff, sizeof (buff))); close(sfd); if (read(cfd, &ch, 1)<0) perror ( "read" ); while ( ch != EOF) { if ((ch>= 'a' && ch<= 'z' ) || (ch>= 'A' && ch<= 'Z' )) ch^=0x20; /* EXORing 6th bit will result in change in case */ if (write(cfd, &ch, 1)<0) perror ( "write" ); if (read(cfd, &ch, 1)<0) perror ( "read" ); } close(cfd); return 0; } close(cfd); } } |
Let’s see what we’ve done here. At line 23, after getting the socket descriptor cfd
from the call to accept, we forked the server. The child process (where pid==0
) closes the listening descriptor with close(sfd)
, does the work the server is intended to do, and when finished, closes the descriptor and returns (see lines 27-39).
The server, on the other hand, where pid>0
, just closes the cfd
(line 39), and is again ready to accept more connections. Thus, for each incoming connection, a new server process will be created.
Another method of doing this is using threads, but we’re not getting into that right now.
Now, compile, run and see the server running and handling multiple clients. See Figure 1 — I’m running the server on a network now (;) though these clients are VirtualBox running VMs with Backtrack (192.168.1.19) and Arch (192.168.1.4), and Android phone running ConnectBot to create a TCP connection.
Figure 1: Server screen
Also run netstat -a | grep 1205
to check for current network connections; I’ve greped 1205, our port, to show only connections to our server (see Figure 2).
Figure 2: Output of netstat
We can also see the parent server process LISTENing and the ESTABLISHED connections, with IPs and ports.
We added signal(SIGCHLD, SIG_IGN)
to the code to prevent child processes going into a zombie state. A child process, when it finishes (a clean termination or killed by some signal), returns the exit status to its parent process, which is notified by the SIGCHLD
signal sent by the system. If the parent doesn’t handle this signal, the child process still uses some memory, and remains in a zombie state. If the parent finishes before the child, or doesn’t collect the status and terminates, the status will be provided to the parent of all processes, i.e., init
with pid 1
.
Let’s examine some code from the signal()
man page:
#include <signal.h> typedef void (*sighandler_t)( int ); sighandler_t signal ( int signum, sighandler_t handler); |
signal()
sets the disposition of the signal signum
to handler, which is either SIG_IGN
, SIG_DFL
, or the address of a programmer-defined function (a “signal handler”). It also indicates that the behaviour of signal()
differs among different Linux and UNIX versions, and tells us to usesigaction()
instead.
The problem is whether the blocked system call, after running the signal handler, will be restarted or not. We can look at this later, when we write our own signal handler — or just go check the sigaction
structure in the man pages.
Now, just receive the signal and ignore it by setting handler to SIG_IGN
. This will not let the child enter a zombie state. What if the parent finishes before the child (though not the case here, because the server is in an infinite loop)? In that case, the parent can wait for the child usingwait()
or waitpid()
calls, of which waitpid()
is preferred.
These system calls wait for a state change in the child, such as the child being terminated or stopped by a signal, or resumed by a signal (check the man pages).
A little science
Now, before moving further, let’s talk about TCP here since better code requires a sound understanding of the basics.
In Figure 3, the rectangles represent the states, and the edges, the transitions.
Figure 3: TCP state diagram
We need to visualise the server and client in the diagram. Two edges come out of the CLOSED state; one is Active Open. This transition occurs when a client sends a SYN packet to the server, and the system is initiating the connection. Another is Passive Open, where the server enters the listen state and waits for connections.
First, from the client side, after the SYN is sent the client enters the SYN_SENT
state. Now, after receiving the SYN from the server and sending SYN and ACK, it transitions to ESTABLISHED. From the ESTABLISHED state (where communication takes place), sending a FIN will initiate an Active Close to terminate the connection, and enter the FIN_WAIT_1
state. Receiving ACK will move it to the FIN_WAIT_2
state.
Receiving the FIN from the server will result in sending the ACK, and going to the TIME_WAIT
state. In this state, the system waits for twice the MSL (maximum segment lifetime); the recommended value in RFC 1337 is 120 seconds, Linux implements 60 seconds. This state helps reliable termination ofconnections in case of lost packets, and allows old duplicate segments to expire in the network. Finally, it goes to the CLOSED state.
For the server, passive open is the LISTEN state. Receiving a SYN results in sending SYN and ACK, and going to the SYN_RCVD
state. Receiving an ACK will take the server to the ESTABLISHED state for data communication. Then, receiving a FIN will result in sending an ACK, and will initiate the passive close and it going to the CLOSE_WAIT
state.
After the operation completes, the server sends the FIN, and transitions to the LAST_ACK
state. On receiving the ACK, it will terminate the connection and go to the CLOSED state.
Here we can see the “ThreeHandshake” — the exchange of three packets to establish a TCP connection. It is initiated when the client calls connect()
. Packet 1 is SYN x from client to server; Packet 2 is ACK x+1 and SYN y from server to client; and Packet 3 is ACK y+1 from client to server. Here, x is the sequence number from the client, and y the sequence number from the server.
To terminate the connection, we need four packets. The client calls close()
to initiate termination: Packet 1 is FIN m from client to server; and Packet 2 is ACK m+1 from server to client. Now, the server finishes the operation, and then calls close()
and sends its FIN n. The client sends ACK n+1 to terminate the connection.
Here’s where I close the connection, even though it was short this time! Next month, I’ll be back with a new connection to socket programming… you can now ACK my FIN!
The Socket API, Part 3: Concurrent Servers的更多相关文章
- Creating Your Own Server: The Socket API, Part 1
转:http://www.linuxforu.com/2011/08/creating-your-own-server-the-socket-api-part-1/ By Pankaj Tanwar ...
- 刨根问底系列(3)——关于socket api的原子操作性和线程安全性的探究和实验测试(多线程同时send,write)
多个线程对同一socket同时进行send操作的结果 1. 概览 1.1 起因 自己写的项目里,为了保证连接不中断,我起一个线程专门发送心跳包保持连接,那这个线程在send发送数据时,可能会与主线程中 ...
- Creating Your Own Server: The Socket API, Part 2
转:http://www.linuxforu.com/2011/09/creating-your-own-server-the-socket-api-part-2/ By Pankaj Tanwar ...
- UNIX网络编程——SOCKET API和TCP STATE的对应关系_三次握手_四次挥手及TCP延迟确认
在socket系统调用中,如何完成三次握手和四次挥手: SOCK_DGRAM即UDP中的connect操作知识在内核中注册对方机器的IP和PORT信息,并没有建立连接的过程,即没有发包,close也不 ...
- c/c++ socket API 调用后的错误判断 perror errno
socket API 调用后的错误判断 perror errno 调用完socket API后,需要判断调用是否成功与失败.如果失败,会自动设置errno(是个整数), 并且用perror可以打印出具 ...
- JNI 和 socket api
1.JavaVM 和 JNIEnvJNIEnv是一个与线程相关的变量,不同线程的JNIEnv彼此独立.JavaVM是虚拟机在JNI层的代表,在一个虚拟机进程中只有一个JavaVM,因此该进程的所有线程 ...
- LwIP - raw/callback API、协议栈API(sequential API)、BSD API(或者说 SOCKET API)
1.使用raw/callback API编程,用户编程的方法是向内核注册各种自定义的回调函数,回调函数是与内核实现交换的唯一方式. recv_udp, accept_function, sent_tc ...
- socket编程 ------ BSD socket API
伯克利套接字(Berkeley sockets),也称为BSD Socket.伯克利套接字的应用编程接口(API)是采用C语言的进程间通信的库,经常用在计算机网络间的通信. BSD Socket的应用 ...
- Python Socket API 笔记
将上节中的C#该成Python版的容易程度大大超出了我的意料之外.从来没有发现,仅仅用灰尘简单的几句话就实现了该程序的主要功能,可见python的简易和强大之处.这里先对SocketAPI 做一下总结 ...
随机推荐
- bzoj2940: [Poi2000]条纹
2940: [Poi2000]条纹 条纹游戏是一个双人的游戏.所需要的物品有一个棋盘以及三种颜色的长方形条纹,这三种颜色分别是红色.绿色和蓝色.所有的红色条纹的尺寸是c*1,所有的绿色条纹的尺寸是z* ...
- Command Line Tools uninstall
sudo rm -rf /Library/Developer/CommandLineTools
- ResultMap
Mybatis 数据库字段和对象属性的映射 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE ...
- 解决SQLServer2008 Express远程连接出错的问题[Error: 1326错误]
sqlserver2008 Express版本默认是只能本机测试连接,不能被其他客户端访问,原因是因为Express版本的数据库被连接默认的TCP/IP监听是被关闭的,我们可以做一些设置实现我们的远程 ...
- JQ避免出现多次执行一个事件的解决方案
点击按钮之后会多次执行一个事件的话,就在方法结尾加入如下代码,这样的话事件就可以只执行一次了 //避免出现多次执行事件的问题 event.stopPropagation(); 此外,时间的重复绑定也有 ...
- [置顶] 如何在浏览器中打开PDF文件并实现预览的思路与代码
编写项目遇到一个需要在浏览器中打开PDF文件的问题.最终实现效果如下: 其实也就是简单的在浏览器中实现一个打开pdf文件,并有类似预览功能的边框. 其实在网上经常见到类似的页面,在浏览器中打开pdf文 ...
- js关闭当前页面(窗口)的几种方式总结
1. 不带任何提示关闭窗口的js代码 <a href="javascript:window.opener=null;window.open('','_self');window.clo ...
- Base64把图片、文件转码成字符串(互转)
文件Base64以后会翻倍的涨,例如一张52kb的图片 base64以后string文件大小为185kb,在通过string转回图片为135kb 图片转文字: UIImage *_originIm ...
- 1016. 部分A+B (15)
正整数A的“DA(为1位整数)部分”定义为由A中所有DA组成的新整数PA.例如:给定A = 3862767,DA = 6,则A的“6部分”PA是66,因为A中有2个6. 现给定A.DA.B.DB,请编 ...
- SQLServer获取随机数据
1.比较常见和好用的一种 SELECT TOP 10 *, NEWID() AS randomFROM tableORDER BY random --newid函数会随机生成一个guid,很长的一个字 ...