/* * Simple "Hello, World!" server * Ivan Griffin (ivan.griffin@ul.ie) *//* Hellwolf Misty translated */#include /* */#include /* exit() */#include /* memset(), memcpy() */#include /* uname() */#include #include/* socket(), bind(), listen(), accept() */#include #include #include #include /* fork(), write(), close() *//* * constants */const char MESSAGE[] = "Hello, World!\n";const int BACK_LOG = 5;/* *程式需要一個命令列參數:需要綁定的連接埠號碼 */int main(int argc, char *argv[]){int serverSocket = 0,on = 0,port = 0,status = 0,childPid = 0;struct hostent *hostPtr = NULL;char hostname[80] = "";struct sockaddr_in serverName = { 0 };if (2 != argc){fprintf(stderr, "Usage: %s \n", argv[0]);exit(1);}port = atoi(argv[1]);/ * *socket()系統調用,帶有三個參數: *1、參數domain指明通訊域,如PF_UNIX(unix域),PF_INET(IPv4), * PF_INET6(IPv6)等 *2、type指明通訊類型,最常用的如SOCK_STREAM(連線導向可靠方式, * 比如TCP)、SOCK_DGRAM(非連線導向的非可靠方式,比如UDP)等。 *3、參數protocol指定需要使用的協議。雖然可以對同一個協議 * 家族(protocol family)(或者說通訊域(domain))指定不同的協議 * 參數,但是通常只有一個。對於TCP參數可指定為IPPROTO_TCP,對於 * UDP可以用IPPROTO_UDP。你不必顯式制定這個參數,使用0則根據前 * 兩個參數使用預設的協議。 */ serverSocket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);if (-1 == serverSocket){perror("socket()");exit(1);} /* * 一旦套介面被建立,它的運作機制可以通過套介面選項(socket option)進行修改。 *//* * SO_REUSEADDR選項的設定將套介面設定成重新使用舊的地址(IP地址加連接埠號碼)而不等待 * 注意:在Linux系統中,如果一個socket綁定了某個連接埠,該socket正常關閉或程式退出後, * 在一段時間內該連接埠依然保持被綁定的狀態,其他程式(或者重新啟動 的原程式)無法綁定該連接埠。 * * 下面的調用中:SOL_SOCKET代表對SOCKET層進行操作 */ on = 1;status = setsockopt(serverSocket, SOL_SOCKET, SO_REUSEADDR,(const char *) &on, sizeof(on));if (-1 == status){perror("setsockopt(...,SO_REUSEADDR,...)");}/* 當串連中斷時,需要延遲關閉(linger)以保證所有資料都 * 被傳輸,所以需要開啟SO_LINGER這個選項 * linger的結構在/usr/include/linux/socket.h中定義: * struct linger * { * int l_onoff; /* Linger active */ * int l_linger; /* How long to linger */ * }; * 如果l_onoff為0,則延遲關閉特性就被取消。如果非零,則允許套介面延遲關閉。 * l_linger欄位則指明延遲關閉的時間 */{struct linger linger = { 0 };linger.l_onoff = 1;linger.l_linger = 30;status = setsockopt(serverSocket, SOL_SOCKET, SO_LINGER, (const char *) &linger, sizeof(linger));if (-1 == status){perror("setsockopt(...,SO_LINGER,...)");}}/* * find out who I am */status = gethostname(hostname, sizeof(hostname));if (-1 == status){perror("gethostname()");exit(1);}hostPtr = gethostbyname(hostname);if (NULL == hostPtr){perror("gethostbyname()");exit(1);}(void) memset(&serverName, 0, sizeof(serverName));(void) memcpy(&serverName.sin_addr, hostPtr->h_addr, hostPtr->h_length); /* *h_addr是h_addr_list[0]的同義字, *h_addr_list是一組地址的數組 *長度為4(byte)代表一個IP地址的長度 *//* * 為了使伺服器綁定本機所有的IP地址, * 上面一行代碼需要用下面的代碼代替 * serverName.sin_addr.s_addr=htonl(INADDR_ANY); */serverName.sin_family = AF_INET;/* htons:h(host byteorder,主機位元組序) * to n(network byteorder,網路位元組序 * s(short類型) */serverName.sin_port = htons(port);/* 在一個地址(本例中的serverSocket)被建立後 * 它就應該被綁定到我們獲得的套介面。 */status = bind(serverSocket, (struct sockaddr *) &serverName,sizeof(serverName));if (-1 == status){perror("bind()");exit(1);}/* 現在套介面就可以被用來監聽新的串連。 * BACK_LOG指定了未決串連監聽隊列(listen queue for pending connections) * 的最大長度。當一個新的串連到達,而隊列已滿的話,客戶就會得到串連拒絕錯誤。 * (這就是dos拒絕服務的攻擊的基礎)。 */status = listen(serverSocket, BACK_LOG);if (-1 == status){perror("listen()");exit(1);}/* 從這裡開始,套介面就開始準備接受請求,並為他們服務。 * 本例子是用for迴圈來達到這個目的。一旦串連被接受(accpepted), * 伺服器可以通過指標獲得客戶的地址以便進行一些諸如記錄客戶登陸之類的 * 任務。for (;;){struct sockaddr_in clientName = { 0 };int slaveSocket, clientLength = sizeof(clientName);(void) memset(&clientName, 0, sizeof(clientName));slaveSocket = accept(serverSocket, (struct sockaddr *) &clientName, &clientLength);if (-1 == slaveSocket){perror("accept()");exit(1);}childPid = fork();switch (childPid){case -1: /* ERROR */perror("fork()");exit(1);case 0: /* child process */close(serverSocket);if (-1 == getpeername(slaveSocket, (struct sockaddr *) &clientName, &clientLength)){perror("getpeername()");}else{printf("Connection request from %s\n",inet_ntoa(clientName.sin_addr));}/* * Server application specific code * goes here, e.g. perform some * action, respond to client etc. */write(slaveSocket, MESSAGE, strlen(MESSAGE)); /* 也可以使用帶緩衝的ANSI函數fprint, * 只要你記得必要時用fflush重新整理緩衝 */close(slaveSocket);exit(0);default: /* parent process */close(slaveSocket);/* 這是一個非常好的習慣* 父進程關閉子進程的套介面描述符* 正如上面的子進程關閉父進程的套介面描述符。*/ }}return 0;} |