大凡在WIN32平臺上的WINSOCK編程都要經過下列步驟:
定義變量->獲得WINDOCK版本->加載WINSOCK庫->初始化->創建套接字->設置套接字選項->關閉套接字->卸載WINSOCK庫->釋放資源
下面介紹WINSOCK C/S的建立過程:
服務器 客戶端
________________________________________________
1 初始化WSA 1 初始化WSA
____________________________________________________
2 建立壹個SOCKET 2 建立壹個SOCKET
_____________________________________________________
3 綁定SOCKET 3 連接到服務器
_____________________________________________________
4 在指定的端口監聽 4 發送和接受數據
_____________________________________________________
5 接受壹個連接 5 斷開連接
______________________________________________________-
6 發送和接受數據
___________________________________________________
7 斷開連接
__________________________________________________
大家註意,在VC中進行WINSOCK編程時,需要引入如下兩個庫文件:WINSOCK.H(這個是WINSOCK API的頭文件,WIN2K以上支持WINSOCK2,所以
可以用WINSOCK2.H);Ws2_32.lib(WINSOCK API連接庫文件).
使用方式如下:
#include <winsock.h>
#pragma comment(lib,"ws2_32.lib")
下面我們通過具體的代碼演示服務器和客戶端的工作流程:
首先,建立壹個WSADATA結構,通常用wsaData
WSADATA wsaData;
然後,調用WSAStartup函數,這個函數是連接應用程序與winsock.dll的第壹個調用.其中,第壹個參數是WINSOCK 版本號,第二個參數是指向
WSADATA的指針.該函數返回壹個INT型值,通過檢查這個值來確定初始化是否成功.調用格式如下:WSAStartup(MAKEWORD(2,2),&wsaData),其中
MAKEWORD(2,2)表示使用WINSOCK2版本.wsaData用來存儲系統傳回的關於WINSOCK的資料.
if(iResuit=WSAStartup(MAKEWORD(2,2),&wsaData)!=0)
{
printf("WSAStartup failed:%d",GetLastError()); //返回值不等與0,說明初始化失敗
ExitProcess(); //退出程序
}
應用程序在完成對請求的SOCKET庫使用後,要調用WSACleanup函數來接觸SOCKET庫的綁定,並且釋放資源.
註意WSAStartup初始化後,必須建立壹個SOCKET結構來保存SOCKET句柄.
下面我們建立壹個SOCKET.
首先我們建立壹個m_socket的SOCKET句柄,接著調用socket()函數,函數返回值保存在m_socket中.我們使用AF_INFE,SOCK_STREAM,IPPROTO_TCP
三個參數.第壹個表示地址族,AF_INFE表示TCP/IP族,第二個表示服務類型,在WINSOCK2中,SOCKET支持以下三種類型;
SOCK_STREAM 流式套接字
SOCK_DGRAM 數據報套接字
SOCK_RAW 原始套接字
第三個參數表示協議:
IPPROTO_UDP UDP協議 用於無連接數據報套接字
IPPROTO_TCP TCP協議 用於流式套接字
IPPROTO_ICMP ICMP協議用於原始套接字
m_socket=socket(AF_INFE,SOCK_STREAM,IPPROTO_TCP); //創建TCP協議
以下代碼用於檢查返回值是否有錯誤:
if(m_scoket==INVALID_SOCKET)
{
prinrf("Error at socket():%d\n",GetLastError());
WSACleanup(); //釋放資源
return;
}
說明,如果socket()調用失敗,他將返回INVALID_SOCKET.
為了服務器能接受壹個連接,他必須綁定壹個網絡地址,下面的代碼展示如何綁定壹個已經初始化的IP和端口的Socket.客戶端程序用這個
IP地址和端口來連接服務器.
sockaddr_in service;
service.sin_family=AF_INET; //INTERNET地址族
service.sin_addr.s_addr=inet_addr("127.0.0.1"); //將要綁定的本地IP地址
service.sin_port=htons(27015); //27015將要綁定的端口
下面我們調用BIND函數,把SOCKET和SOCKADDR以參數的形式傳入,並檢查錯誤.
if(bind(m_socket,(SOCKADDR*)&SERVICE,sizeof(service))==SOCKET_ERROR)
{
printf("bind() failed.\n");
closesocket(m_socket);
return;
}
當綁定完成後,服務器必須建立壹個監聽隊列,以接受客戶端的請求.listen()使服務器進入監聽狀態,該函數調用成功返回0,否則返回
SOCKET_ERROR.代碼如下:
if(listen(m_socket,1)==SOCKET-ERROR)
{
printf("error listening on socket.\n");
}
服務器端調用完LISTEN()後,如果此時客戶端調用CONNECT()函數,服務器端必須在調用ACCEPT().這樣服務器和客戶端才算正式完成通信程序的
連接動作.
壹旦服務器開始監聽,我們就要指定壹個句柄來表示利用ACCEPT()函數接受的連接,這個句柄是用來發送和接受數據的表示.建立壹個SOCKET句柄
Socket AcceptSocket 然後利用無限循環來檢測是否有連接傳入.壹但有連接請求,ACCEPT()函數就會被調用,並且返回這次連接的句柄.
printf("waitong for a client to connect...\n");
while(1)
{
AcceptSocket=SOCKET_ERROR;
while(AcceptSocket==SOCKET_ERROR)
{
AcceptSocket=accept(m_socket,NULL,NULL);
}
}
下面看客戶端端代碼:
sockaddr_in clientService;
clientService.sin_family=AF_INET; //INTERNET地址族
clientService.sin_addr.s_addr=inet_addr("127.0.0.1"); //將要綁定的本地IP地址
clientService.sin_port=htons(27015); //27015將要綁定的端口
下面調用CONNECT()函數:
if ( connect( m_socket, (SOCKADDR*) &clientService, sizeof(clientService) ) == SOCKET_ERROR)
{
printf( "Failed to connect.\n" );
WSACleanup();
return;
} //如果調用失敗清理退出
//調用成功繼續讀寫數據
_________________________________________________________________________________________________
到這裏,服務器和客戶端的基本流程介紹完畢,下面我們介紹數據交換.
send():
int send
{
SOCKET s, //指定發送端套接字
const char FAR?*buf, //指明壹個存放應用程序要發送的數據的緩沖區
int len, //實際要發送的數據字節數
int flags //壹般設置為0
};
C/S都用SEND函數向TCP連接的另壹端發送數據.
recv():
int recv
{
SOCKET s, //指定發送端套接字
char FAR?*buf, //指明壹個緩沖區 存放RECC受到的數據
int len, //指明BUF的長度
int flags //壹般設置為0
};
C/S都使用RECV函數從TCP連接的另壹端接受數據
下面將完整的程序代碼提供如下,大家可直接編譯運行
首先看客戶端的代碼:
#include <stdio.h>
#include <winsock2.h>
#pragma comment(lib, "ws2_32.lib")
void main() {
// 初始化 Winsock.
WSADATA wsaData;
int iResult = WSAStartup( MAKEWORD(2,2), &wsaData );
if ( iResult != NO_ERROR )
printf("Error at WSAStartup()\n");
// 建立socket socket.
SOCKET client;
client = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP );
if ( client == INVALID_SOCKET ) {
printf( "Error at socket(): %ld\n", WSAGetLastError() );
WSACleanup();
return;
}
// 連接到服務器.
sockaddr_in clientService;
clientService.sin_family = AF_INET;
clientService.sin_addr.s_addr = inet_addr( "127.0.0.1" );
clientService.sin_port = htons( 27015 );
if ( connect( client, (SOCKADDR*) &clientService, sizeof(clientService) ) == SOCKET_ERROR) {
printf( "Failed to connect.\n" );
WSACleanup();
return;
}
// 發送並接收數據.
int bytesSent;
int bytesRecv = SOCKET_ERROR;
char sendbuf[32] = "Client: Sending data.";
char recvbuf[32] = "";
bytesSent = send( client, sendbuf, strlen(sendbuf), 0 );
printf( "Bytes Sent: %ld\n", bytesSent );
while( bytesRecv == SOCKET_ERROR ) {
bytesRecv = recv( client, recvbuf, 32, 0 );
if ( bytesRecv == 0 || bytesRecv == WSAECONNRESET ) {
printf( "Connection Closed.\n");
break;
}
if (bytesRecv < 0)
return;
printf( "Bytes Recv: %ld\n", bytesRecv );
}
return;
}
下面是服務器端代碼:
#include <stdio.h>
#include <winsock2.h>
#pragma comment(lib, "ws2_32.lib")
void main() {
// 初始化
WSADATA wsaData;
int iResult = WSAStartup( MAKEWORD(2,2), &wsaData );
if ( iResult != NO_ERROR )
printf("Error at WSAStartup()\n");
// 建立socket
SOCKET server;
server = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP );
if ( server == INVALID_SOCKET ) {
printf( "Error at socket(): %ld\n", WSAGetLastError() );
WSACleanup();
return;
}
// 綁定socket
sockaddr_in service;
service.sin_family = AF_INET;
service.sin_addr.s_addr = inet_addr( "127.0.0.1" );
service.sin_port = htons( 27015 );
if ( bind( server, (SOCKADDR*) &service, sizeof(service) ) == SOCKET_ERROR ) {
printf( "bind() failed.\n" );
closesocket(server);
return;
}
// 監聽 socket
if ( listen( server, 1 ) == SOCKET_ERROR )
printf( "Error listening on socket.\n");
// 接受連接
SOCKET AcceptSocket;
printf( "Waiting for a client to connect...\n" );
while (1) {
AcceptSocket = SOCKET_ERROR;
while ( AcceptSocket == SOCKET_ERROR ) {
AcceptSocket = accept( server, NULL, NULL );
}
printf( "Client Connected.\n");
server = AcceptSocket;
break;
}
// 發送接受數據
int bytesSent;
int bytesRecv = SOCKET_ERROR;
char sendbuf[32] = "Server: Sending Data.";
char recvbuf[32] = "";
bytesRecv = recv( server, recvbuf, 32, 0 );
printf( "Bytes Recv: %ld\n", bytesRecv );
bytesSent = send( server, sendbuf, strlen(sendbuf), 0 );
printf( "Bytes Sent: %ld\n", bytesSent );
return;
}
本程序僅僅描述了同步的情況!
PS:本文轉自百度貼吧新紅盟吧
============================================================accept()補充
綁定socket後用listen函數去監聽,如果有連接請求就把該請求放到等待隊列裏,等待accept函數來接收。
壹旦accept接收成功就創建壹個新的socket來處理與client的通訊。
accept()函數
準備好了,系統調用accept()會有點古怪的地方的!
妳可以想象發生這樣的事情:有人從很遠的地方通過壹個妳在偵聽(listen())的端口連接(connect())到妳的機器。
它的連接將加入到等待接受(accept())的隊列中。妳調用accept()告訴它妳有空閑的連接。它將返回壹個新的套接字文件描述符!
這樣妳就有兩個套接字了,原來的壹個還在偵聽妳的那個端口,新的在準備發送(send())和接收(recv())數據。這就是這個過程!
函數是這樣定義的:
#include <sys/socket.h>
int accept(int sockfd, void *addr, int *addrlen);
sockfd相當簡單,是和isten()中壹樣的套接字描述符。addr是個指向局部的數據結構sockaddr_in的指針。
這是要求接入的信息所要去的地方(妳可以測定哪個地址在哪個端口呼叫妳)。
在它的地址傳遞給accept之前,addrlen是個局部的整形變量,設置為 sizeof(struct sockaddr_in)
accept將不會將多余的字節給addr。如果妳放入的少些,那麽它會通過改變 addrlen 的值反映出來。
同樣,在錯誤時返回-1,並設置全局錯誤變量 errno。
現在是妳應該熟悉的代碼片段。
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#define MYPORT 3490 /*用戶接入端口*/
#define BACKLOG 10 /* 多少等待連接控制*/
main()
{
int sockfd, new_fd; /* listen on sock_fd, new connection on new_fd */
struct sockaddr_in my_addr; /* 地址信息 */
struct sockaddr_in their_addr; /* connector's address information*/
int sin_size;
/* don't forget your error checking for these calls: */
sockfd = socket(AF_INET, SOCK_STREAM, 0); /* 錯誤檢查*/
my_addr.sin_family = AF_INET; /* host byte order */
my_addr.sin_port = htons(MYPORT); /* short, network byte order */
my_addr.sin_addr.s_addr = INADDR_ANY; /* auto-fill with my IP */
bzero(&(my_addr.sin_zero); /* zero the rest of the struct */
bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr));
listen(sockfd, BACKLOG);
sin_size = sizeof(struct sockaddr_in);
new_fd = accept(sockfd, &their_addr, &sin_size);
...
...
}
註意,在系統調用 send() 和 recv() 中妳應該使用新的套接字描述符 new_fd。
如果妳只想讓壹個連接進來,那麽妳可以使用 close() 去關閉原來的文件描述符sockfd來避免同壹個端口更多的連接。