1. 程式人生 > >Winsock(3) 編寫一個TCP服務端

Winsock(3) 編寫一個TCP服務端

本章介紹如何編寫一個 Winsock TCP/IP服務端來接收客戶連線請求 通訊分為面向連線通訊(Connection-Oriented Communication 如TCP)和非連線通訊(Connectionless Communication 如UDP)。筆記(3)至筆記(6)將介紹前者

SOCKET 是Winsock中獨立的一個型別,用來表示一個連線的控制代碼 它的定義如下:

typedef UINT_PTR SOCKET;
typedef _W64 unsigned int UINT_PTR, *PUINT_PTR;

在Winsock中,有兩種基本通訊方式:面向連線傳輸模式、無連線傳輸模式 IP協議族中,TCP/IP協議做到了面向連線通訊,TCP提供零錯誤資料傳輸機制,在傳送方和接收方建立一條虛擬的連線. 服務端是一個程序,它等待若干客戶連線並相應它們的請求. 服務端在一個周知地址上監聽連線,在TCP/IP中,周知埠是本地介面的IP地址和埠號. 不同的協議有不同的地址組成方式因此有不同的地址命名方法.

建立一個服務端的步驟

  1. socket()/WSASocket(); //建立一個socket
  2. bind(); //繫結一個周知地址
  3. listen(); //設定監聽模式
  4. accept()/WSAAccept(); //接收連線
1.socket()/WSASocket(); //建立一個socket.

有兩個建立Socket的方法:socket()、WSASocket().

SOCKET socket(
    int af,
    int type,
    int protocol
);

int af:一個地址描述。目前僅支援AF_INET格式,也就是說ARPA Internet地址格式 int type:指定socket型別。新套介面的型別描述型別,如TCP(SOCK_STREAM)和UDP(SOCK_DGRAM)。常用的socket型別有,SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等等 int protocol:顧名思義,就是指定協議。套介面所用的協議。如呼叫者不想指定,可用0。常用的協議有,IPPROTO_TCP、IPPROTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,它們分別對應TCP傳輸協議、UDP傳輸協議、STCP傳輸協議、TIPC傳輸協議

Winsock提供了四個很有用的函式來控制socket的選項和行為:setsockopt()、getsockopt()、ioctlsocket()、WSAIoctl().將在後面詳細介紹

2.bind(); //繫結一個周知地址
int bind(
    SOCKET s,    //服務端socket
    const struct sockaddr FAR * name,    //SOCKADDR
    int namelen    //name長度
);

使用程式碼如下

SOCKET s;
SOCKADDR_IN InternetAddr;
s=socket(AF_INET,SOCK_STREAM,
IPPROTO_TCP); //建立socket INT nPortID=5150; InternetAddr.sin_family=AF_INET; InternetAddr.sin_addr.s_addr=htonl(INADDR_ANY); //INADDR_ANY引數表示該服務端接收任意地址往該埠傳送的訊息 InternetAddr.sin_port=htons(nPortID); bind(s,(SOCKADDR*)&InternetAddr,sizeof(InternetAddr)); //顯式轉換

錯誤時返回SOCKET_ERROR. 常見錯誤碼:

  • WSAEADDRINUSE:該IP埠已被佔用或者處於TIME_WAIT狀態.
  • WSAEFAULT:已經繫結過後再一次繫結.
3.listen(); //設定監聽模式
int listen(
SOCKET s,
int backlog    //掛起連線佇列最大長度
);

設定backlog引數後仍然需要由程式底層來進行適當替換,替換為一個適合的接近值,並且使用者無法得知確切的值.一般設定為5. 當掛起佇列滿時,連線請求會失敗,返回WSAECONNREFUSED 常見錯誤碼:

  • WSAEADDRINUSE:該IP埠已被佔用或者處於TIME_WAIT狀態.
  • WSAEFAULT:listen()之前會進行bind().
4.accept()/WSAAccept(); //接收連線

其實還有AcceptEx()函式,是accept()的擴充套件,在後面會介紹

SOCKET accept(
    socket s,    //監聽狀態繫結的socket
    struct sockaddr FAR * addr,    //有效的SOCKADDR_IN地址
    int FAR * addrlen    //SOCKADDR_IN的長度
);

該函式返回一個關聯到請求連線的客戶的socket描述符,然後使用這個socket來進行後續一系列操作. 服務端監聽socket將持續存在來監聽其他客戶的請求. 發生錯誤時,返回INVALID_SOCKET錯誤. 常見錯誤碼:

  • WSAEWOULDBLOCK:監聽socket在非同步或非阻塞模式並且無連線可以接受.

一個完整的 Winsock TCP/IP 服務端程式碼如下

int main(void)
{
WSADATA wsaData;
SOCKET ListeningSocket;
SOCKET NewConnection;
SOCKADDR_IN ServerAddr;
SOCKADDR_IN ClientAddr;
int port=5150;
int ClientAddrLen;

WSAStartup(MAKEWORD(2,2),&wsaData);    //初始化 Winsock 2.2 版本
ListeningSocket=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);    //建立一個 socket 來監聽客戶連線
ServerAddr.sin_family=AF_INET;    //填充 SOCKADDR_IN 資料結構
ServerAddr.sin_port=htons(port);
ServerAddr.sin_addr.s_addr=htonl(ADDR_ANY);
bind(ListeningSocket,(SOCKADDR*)&ServerAddr,sizeof(ServerAddr));    //繫結一個周知地址
listen(ListeningSocket,5);    //設定監聽模式
ClientAddrLesizeof(ClientAddr);    //顯示指定ClientAddrLen大小
NewConnection=accept(ListeningSocket,(SOCKADDR*)&ClientAddr,&ClientAddrLen);
//接受一個到來的連線,注意!最後一個引數需要自己顯式指定!
//這裡你通過這些socket可以做兩件事
//1.通過ListeningSocket再次呼叫accept()來接受其他連線
//2.通過NewConnection來發送/接受資料
//當你做完這兩件事情時必須要關閉這些socket
//socket的關閉將在後面介紹
closesocket(ListeningSocket);    //關閉ListeningSocket
closesocket(NewConnection);    //關閉NewConnection
WSACleanup();    //關閉Winsock
return 0;
}

注意 accept()函式的最後一個引數需要顯式指定大小! ClientAddrLesizeof(ClientAddr); //顯式指定ClientAddrLen大小 好了,到目前為止你已經可以編寫一個 Winsock TCP/IP服務端來接收客戶連線請求了.