求一个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来避免同一个端口更多的连接。

服务器创建一个监听线程,一个接收线程,一个发送线程!
各创建一个成员变量m_Socket
客户端创建一个接收线程,一个发送线程~

客户端连接服务器时会客户端和服务器都会产生一个socket 保存在m_Socket中
窗口作为主线程,只负责显示数据/当前状态

具体程序很难发的。给你部分代码吧

创建线程:
HANDLE hThread = ::CreateThread(NULL, 0, RecvData, this, 0, 0);
::CloseHandle(hThread);
//RecvData为接收函数
DWORD WINAPI RecvData(
LPVOID lpParameter // thread data
)
{
//记得把参数强制转化成窗口指针
CSocketDlg *dlg = (CSocketDlg*)lpParameter;
int nRecv = 0;
char buffer[1024];
while(1)
{
memset(buffer, 0, 1024);
int nRecv = recv(dlg->m_Socket, buffer, 1024, 0);
if(SOCKET_ERROR == nRecv)
{
int err = WSAGetLastError();
if(WSAETIMEDOUT == err || WSAENETDOWN == err) //断开连接
break;
}

if(0 == nRecv) //对方关闭了连接
break;
}
}

其他同理~!

客户端和服务器端的话已经是C/S模型了.应该就是两个进程之间的通信了,为什么要用多线程?

c++ 求助socket多线程网络通信怎么实现并发~

并发访问,多个客户端连接服务器,最简单暴力的方式就是你服务端监听线程accept到一个客户端的连接,处理好后立马再开一个监听线程继续accept阻塞等待。要想非阻塞的方式,建议使用下select模型的方式。


#15733898752# 如何解决阻塞函数sendto和recvfrom - ******
#姓知# recvfrom这个函数最好放在线程里,因为这个函数一但运行,效果就像是一个for(;;);除非收到消息否则不会停下来,连文字都输入不了,无法正常聊天了,只能轮流说.百度查怎么开启线程.

#15733898752# TCP MFC对话框C++网络编程多线程编程怎么实现呢? - ******
#姓知# 你在阻塞的代码处使用_beginthread创建一个线程来执行相关代码不就可以了.

#15733898752# C++中如何实现进程和线程之间的通讯? - ******
#姓知# 进程和线程不是一个层级上的概念啊,本进程之间的线程共享堆栈区,数据都是共享的.进程间的通讯方式有管道、命名管道、共享内存、信号量、socket等

#15733898752# VC++ socket多线程编程问题 - ******
#姓知# WSAStartup()和WSACleanup()在主线程调用一次就行,一般在main函数调用.不调用WSACleanup()在进程结束后系统会帮你回收资源.建议还是调用WSACleanup(),写出程序看起来逻辑严谨.

#15733898752# c++ 多线程互斥锁会阻塞线程吗 - ******
#姓知# 某线程正在使用互斥的时候,其他的线程去获取它就会被阻塞,直到线程放弃互斥为止 反正一次只能有一个线程使用互斥

#15733898752# 有没有谁能成功地用TServerSocket编过多线程阻塞方式的通讯程序??? ******
#姓知# 根据delphi自带帮助,就有例子,先定义一个socket类,在ongetthread时创建,传入只可,两个事件 OnClientConnect与OnClientDisconnect,第一个好像没起作用,但ongetthread可以知道客户连接,OnClientDisconnect是可以知道客户断开,我做过相关测试,见笑

#15733898752# c/c++ linux c 多线程 pthread - detach(id); phthread - join(id,0); - ******
#姓知#是这样的,pthread_join()这个函数在多线程开发中主线程主要用来获得子线程结束的状态以便回收子线程的资源. 但是有时候会出现你调用pthread_join()后,子线程还在运作,这样调用者(调用pthread_join的者)可能就会被阻...

#15733898752# 求编程领域上一些经典算法同时也是程序员必须掌握的算法 - ******
#姓知# 这是我在一个论坛里看到的,你也参考参考吧.C++的虚函数====================== C++使用虚函数实现了其对象的多态,C++对象的开始四个字节是指向虚函数表的指针,其初始化顺序是先基类后派生类,所以该虚函数表永远指向最后一...

#15733898752# 如何实现多串口的同步通?C#如何实现多串口的同步通讯 ******
#姓知# 每个串口都可以独立操作,你需要打开多少个串口都可以啊,你说的同步就是多线程阻塞式通讯,开独立的线程用于一个串口的读写操作就可以了.

#15733898752# c++控制台程序线程问题 - ******
#姓知# 设计问题 (1)tcp是基于连接的、双向的,你一个tcp连接对应两个线程,一个收、一个发,很少有这么设计的(虽然逻辑上是可以的);一般一个线程处理一个连接,同时做收发处理 (2)你的代码不全,我没太看明白;但一般服务器主线程循环调用accept,接收新的连接请求(并生成新的socket),然后把这个socket传递给新线程处理.代码问题 (1)你的ClientThreadSend和ClientThreadAccept创建完,后面就直接CloseHandle;很可能线程还没有执行就被CloseHandle掉了;如果你的线程只处理简单事情、没有无限循环,那么就不需要主线程CloseHandle,让线程自己执行完退出就可以了.

  • 微服务架构的分布式事务问题如何处理?
  • 答:分布式系统架构中,分布式事务问题是一个绕不过去的挑战。而微服务架构的流行,让分布式事问题日益突出!下面我们以电商购物支付流程中,在各大参与者系统中可能会遇到分布式事务问题的场景进行详细的分析!如上图所示,假设三大...

  • vc++一个进程负责写入共享区,然后多个进程读取,会有什么异常吗?_百度...
  • 答:应用层通过传输层进行数据通信时,TCP和UDP会遇到同时为多个应用程序进程提供并发服务的问题。多个TCP连接或多个应用程序进程可能需要通过同一个TCP协议端口传输数据。为了区别不同的应用程序进程和连接,许多计算机操作系统为应用...

  • javascript中如何判断一个函数是async函数
  • 答:的程序语言,应用在电视机、电话、闹钟、烤面包机等家用电器的控制和通信。...分布式、解释性、健壮、安全与系统无关、可移植、高性能、多线程和动态的...Sun计算机公司的一个叫做帕特里克·诺顿的工程师被公司自己开发的C++和C语言...

  • Tomcat篇02-整体架构和I/O模型
  • 答:尽管I/O操作阻塞,但这种模式比单线程处理的性能明显高了,它不用等到第一个请求处理完才处理第二个,而是并发地处理客户端请求,客户端连接与服务器端处理线程的比例是 1:1 。 多线程阻塞I/O模型的特点:支持对多个客户端并发响应,...

  • 跪求Winsock技术概述
  • 答:3、阻塞处理例程 阻塞处理例程(blocking hook,阻塞钩子)是WINDOWS SOCKETS实现...该模式的建立基于以下两点:1、非对等作用;2、通信完全是异步的。客户机/...在抢先式的多任务操作系统中(WinNt、Win2K)采用多线程方式效率基本达到异步...

  • 请教高手一个问题
  • 答:25.一个类只有一个超类,但一个类能实现多个接口。Java中的一个重要接口:Cloneable 26.接口和回调.编程一个常用的模式是回调模式,在这种模式中你可以指定当一个特定时间发生时回调对象上的方法。 例:ActionListener 接口监听. 类似的...

  • 求深信服技术支持笔试题目(赏20分)
  • 答:4以下哪些通信方式是可靠的通讯方式1信号2管道3消息4tcp 5udp 6串口I/O可靠:信号 管道 tcp可以在不同主机之间的:Unix的启动顺序排序。(A) Rc.X (B) Rc.sysinit (C) login (D) initDBAC进程的几个基本状态:就绪、执行、阻塞...

  • Java网络编程精解的目录
  • 答:3.6 创建多线程的服务器 653.6.1 为每个客户分配一个线程 653.6.2 创建线程池 673.6.3 使用JDK类库提供的线程池 723.6.4 使用线程池的注意事项 743.7 关闭服务器 763.8 小结 803.9 练习题 81第4章 非阻塞通信 834.1 线程...

  • 如何用 python 搭建一个邮件服务器
  • 答:如果你可以从底层socket开始,实现一个完整的Python服务器,支持用户层的协议,并处理好诸如MVC(Model-View-Control)、多线程(threading)等问题,并整理出一套清晰的函数或者类,作为接口(API)呈现给用户,你就相当于设计了一个框架。socket...

  • 计算机网络原理的目录
  • 答:第1章计算机网络概述1.1 计算机网络及其分类计算机网络,是指将地理位置不同的具有独立功能的多台计算机及其外部设备,通过通信线路连接起来,在网络操作系统,网络管理软件及网络通信协议的管理和协调下,实现资源共享和信息传递的计算机系统。

    为传递更多家电数码信息,若有事情请联系
    数码大全网