當前位置:編程學習大全網 - 編程語言 - 求壹個C++多線程阻塞模式通信的例子

求壹個C++多線程阻塞模式通信的例子

關於winsock服務器和客戶端編程2008年12月28日 星期日 23:22在網絡編程中,最常用和最基礎的就是WINSOCK. 現在我們討論WINDOWS下的SOCKET編程.

大凡在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來避免同壹個端口更多的連接。

  • 上一篇:楊進京多大了
  • 下一篇:助聽器怎麽選?
  • copyright 2024編程學習大全網