# 網絡,第 4 部分:構建一個簡單的 TCP 服務器
> 原文:<https://github.com/angrave/SystemProgramming/wiki/Networking%2C-Part-4%3A-Building-a-simple-TCP-Server>
## 什么是`htons`以及何時使用?
整數可以首先表示最低有效字節或最高有效字節。只要機器本身內部一致,任何一種方法都是合理的。對于網絡通信,我們需要對商定的格式進行標準化。
`htons(xyz)`以網絡字節順序返回 16 位無符號整數'short'值 xyz。 `htonl(xyz)`以網絡字節順序返回 32 位無符號整數'long'值 xyz。
這些功能被讀作“主機到網絡”;反函數(ntohs,ntohl)將網絡有序字節值轉換為主機有序排序。那么,是主機訂購 little-endian 還是 big-endian?答案是 - 這取決于你的機器!它取決于運行代碼的主機的實際體系結構。如果體系結構恰好與網絡排序相同,那么這些函數的結果就是參數。對于 x86 機器,主機和網絡訂購 _ 與 _ 不同。
簡介:每當您讀取或寫入低級 C 網絡結構(例如端口和地址信息)時,請記住使用上述功能以確保正確轉換為/從數據庫格式轉換。否則顯示或指定的值可能不正確。
## 用于創建服務器的“4 大”網絡呼叫是什么?
創建 TCP 服務器所需的四個系統調用是:`socket`,`bind` `listen`和`accept`。每個都有特定的目的,應按上述順序調用
端口信息(由 bind 使用)可以手動設置(許多較舊的僅使用 IPv4 的 C 代碼示例執行此操作),或使用`getaddrinfo`創建
我們以后也會看到 setsockopt 的例子。
## 調用`socket`的目的是什么?
為網絡通信創建端點。一個新的插座本身并不是特別有用;雖然我們已經指定了數據包或基于流的連接,但它并未綁定到特定的網絡接口或端口。相反,套接字返回一個網絡描述符,可以用于以后調用 bind,listen 和 accept。
## 調用`bind`的目的是什么
`bind`調用將抽象套接字與實際網絡接口和端口相關聯。可以在 TCP 客戶端上調用 bind,但是通常不需要指定傳出端口。
## 調用`listen`的目的是什么
`listen`調用指定傳入的未處理連接數的隊列大小,即尚未通過`accept`分配網絡描述符的隊列大小。高性能服務器的典型值為 128 或更多。
## 為什么服務器套接字被動?
服務器套接字不會主動嘗試連接到另一臺主機;相反,他們等待傳入的連接。此外,當對等設備斷開連接時,服務器套接字不會關閉。相反,當遠程客戶端連接時,它會立即被撞到未使用的端口號以供將來通信。
## 調用`accept`的目的是什么
初始化服務器套接字后,服務器調用`accept`以等待新連接。與`socket` `bind`和`listen`不同,此調用將被阻止。即,如果沒有新連接,則此呼叫將阻止,并且僅在新客戶端連接時返回。
注意`accept`調用返回一個新的文件描述符。此文件描述符特定于特定客戶端。將原始服務器套接字描述符用于服務器 I / O 然后想知道為什么網絡代碼失敗是常見的編程錯誤。
## 創建 TCP 服務器的難點是什么?
* 使用被動服務器套接字的套接字描述符(如上所述)
* 未指定 getaddrinfo 的 SOCK_STREAM 要求
* 無法重新使用現有端口。
* 不初始化未使用的 struct 條目
* 如果當前正在使用端口,則`bind`調用將失敗
注意,端口是每臺機器 - 不是每個進程或每個用戶。換句話說,當另一個進程正在使用該端口時,您無法使用端口 1234。更糟糕的是,端口在進程完成后默認為“綁定”。
## 服務器代碼示例
一個工作簡單的服務器示例如下所示。請注意,此示例不完整 - 例如,它不會關閉套接字描述符,也不會釋放由`getaddrinfo`創建的內存
```c
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <unistd.h>
#include <arpa/inet.h>
int main(int argc, char **argv)
{
int s;
int sock_fd = socket(AF_INET, SOCK_STREAM, 0);
struct addrinfo hints, *result;
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;
s = getaddrinfo(NULL, "1234", &hints, &result);
if (s != 0) {
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s));
exit(1);
}
if (bind(sock_fd, result->ai_addr, result->ai_addrlen) != 0) {
perror("bind()");
exit(1);
}
if (listen(sock_fd, 10) != 0) {
perror("listen()");
exit(1);
}
struct sockaddr_in *result_addr = (struct sockaddr_in *) result->ai_addr;
printf("Listening on file descriptor %d, port %d\n", sock_fd, ntohs(result_addr->sin_port));
printf("Waiting for connection...\n");
int client_fd = accept(sock_fd, NULL, NULL);
printf("Connection made: client_fd=%d\n", client_fd);
char buffer[1000];
int len = read(client_fd, buffer, sizeof(buffer) - 1);
buffer[len] = '\0';
printf("Read %d chars\n", len);
printf("===\n");
printf("%s\n", buffer);
return 0;
}
```
## 為什么我的服務器無法重新使用該端口?
默認情況下,套接字關閉時不會立即釋放端口。相反,端口進入“TIMED-WAIT”狀態。這可能會在開發過程中導致嚴重的混淆,因為超時可能會使有效的網絡代碼看起來失敗。
為了能夠立即重新使用端口,請在綁定到端口之前指定`SO_REUSEPORT`。
```c
int optval = 1;
setsockopt(sfd, SOL_SOCKET, SO_REUSEPORT, &optval, sizeof(optval));
bind(....
```
這是[對`SO_REUSEPORT`](http://stackoverflow.com/questions/14388706/socket-options-so-reuseaddr-and-so-reuseport-how-do-they-differ-do-they-mean-t) 的擴展 stackoverflow 介紹性討論。
- UIUC CS241 系統編程中文講義
- 0. 簡介
- #Informal 詞匯表
- #Piazza:何時以及如何尋求幫助
- 編程技巧,第 1 部分
- 系統編程短篇小說和歌曲
- 1.學習 C
- C 編程,第 1 部分:簡介
- C 編程,第 2 部分:文本輸入和輸出
- C 編程,第 3 部分:常見問題
- C 編程,第 4 部分:字符串和結構
- C 編程,第 5 部分:調試
- C 編程,復習題
- 2.進程
- 進程,第 1 部分:簡介
- 分叉,第 1 部分:簡介
- 分叉,第 2 部分:Fork,Exec,等等
- 進程控制,第 1 部分:使用信號等待宏
- 進程復習題
- 3.內存和分配器
- 內存,第 1 部分:堆內存簡介
- 內存,第 2 部分:實現內存分配器
- 內存,第 3 部分:粉碎堆棧示例
- 內存復習題
- 4.介紹 Pthreads
- Pthreads,第 1 部分:簡介
- Pthreads,第 2 部分:實踐中的用法
- Pthreads,第 3 部分:并行問題(獎金)
- Pthread 復習題
- 5.同步
- 同步,第 1 部分:互斥鎖
- 同步,第 2 部分:計算信號量
- 同步,第 3 部分:使用互斥鎖和信號量
- 同步,第 4 部分:臨界區問題
- 同步,第 5 部分:條件變量
- 同步,第 6 部分:實現障礙
- 同步,第 7 部分:讀者編寫器問題
- 同步,第 8 部分:環形緩沖區示例
- 同步復習題
- 6.死鎖
- 死鎖,第 1 部分:資源分配圖
- 死鎖,第 2 部分:死鎖條件
- 死鎖,第 3 部分:餐飲哲學家
- 死鎖復習題
- 7.進程間通信&amp;調度
- 虛擬內存,第 1 部分:虛擬內存簡介
- 管道,第 1 部分:管道介紹
- 管道,第 2 部分:管道編程秘密
- 文件,第 1 部分:使用文件
- 調度,第 1 部分:調度過程
- 調度,第 2 部分:調度過程:算法
- IPC 復習題
- 8.網絡
- POSIX,第 1 部分:錯誤處理
- 網絡,第 1 部分:簡介
- 網絡,第 2 部分:使用 getaddrinfo
- 網絡,第 3 部分:構建一個簡單的 TCP 客戶端
- 網絡,第 4 部分:構建一個簡單的 TCP 服務器
- 網絡,第 5 部分:關閉端口,重用端口和其他技巧
- 網絡,第 6 部分:創建 UDP 服務器
- 網絡,第 7 部分:非阻塞 I O,select()和 epoll
- RPC,第 1 部分:遠程過程調用簡介
- 網絡復習題
- 9.文件系統
- 文件系統,第 1 部分:簡介
- 文件系統,第 2 部分:文件是 inode(其他一切只是數據...)
- 文件系統,第 3 部分:權限
- 文件系統,第 4 部分:使用目錄
- 文件系統,第 5 部分:虛擬文件系統
- 文件系統,第 6 部分:內存映射文件和共享內存
- 文件系統,第 7 部分:可擴展且可靠的文件系統
- 文件系統,第 8 部分:從 Android 設備中刪除預裝的惡意軟件
- 文件系統,第 9 部分:磁盤塊示例
- 文件系統復習題
- 10.信號
- 過程控制,第 1 部分:使用信號等待宏
- 信號,第 2 部分:待處理的信號和信號掩碼
- 信號,第 3 部分:提高信號
- 信號,第 4 部分:信號
- 信號復習題
- 考試練習題
- 考試主題
- C 編程:復習題
- 多線程編程:復習題
- 同步概念:復習題
- 記憶:復習題
- 管道:復習題
- 文件系統:復習題
- 網絡:復習題
- 信號:復習題
- 系統編程笑話