面向连接到的方式进行socket通信
- TCP Socket通信的服务器端:
- 首先使用WSAStartup函数来初始化网络环境。
- 调用socket(AF_INET,SOCKET_STREAM,0)函数来创建一个套接字。
- 调用bind函数将本地地址与刚创建的套接字关联起来。
- 调用listen函数监听客户端发向该套接字的连接请求。
- 客户端的连接请求放在连接请求队列里,服务器调用accept函数从连接请求队列中取出第一个请求,创建一个新的位置服务的套接字,该套接字处理所有与该客户交互操作。而服务器进程的监听套接字这时继续处理来自其他客户的连接请求,直到因队列空而等待新的连接请求的到来。
下面是并发式服务器的结构:
代码实现:
void CServerDlg::OnBnClickedAcceptdata()
{
// TODO: 在此添加控件通知处理程序代码
InitSocket(); //初始化套接字
RECVPARAM *pRecvParam = new RECVPARAM;
pRecvParam->sock = m_socket;
pRecvParam->hwnd = m_hWnd;
// RecvProc 线程函数
HANDLE hThread = CreateThread(NULL, 0, RecvProc, (LPVOID)pRecvParam, 0, NULL);
CloseHandle(hThread);
//::PostMessage(AfxGetMainWnd()->m_hWnd, WM_PACKETDATA, 0, (LPARAM)str_toll_package);
}
DWORD WINAPI RecvProc(LPVOID lpParameter)
{
HWND hwnd = ((RECVPARAM * )lpParameter)->hwnd; //获取当前对话框的句柄
SOCKET sock = ((RECVPARAM * )lpParameter)->sock;
//接收数据
int len = sizeof(SOCKADDR); //返回的地址长度
if(listen(sock,10) == SOCKET_ERROR) //监听客户端的连接, 只是设置socket 为listen 模式,最大连接数为10。
{
// MessageBox("Listen failed");
return 0;
}
while(1)
{
SOCKADDR_IN addrClient; //接收发送端的地址
sockConn = accept(sock, (SOCKADDR *) &addrClient, &len);
if(sockConn == SOCKET_ERROR){
break;
}
RECVPARAM *pTCPParam = new RECVPARAM;
pTCPParam->sock = sockConn;
pTCPParam->hwnd = hwnd;
HANDLE hThread = CreateThread(NULL, 0, TCPProc, (LPVOID)pTCPParam, 0, NULL);
CloseHandle(hThread);
}
closesocket(sock);//关闭监听创建的套接字
WSACleanup();
system("pause");
Sleep(1000);
return 0;
}
DWORD WINAPI TCPProc(LPVOID lpParameter)
{
HWND hwnd = ((RECVPARAM * )lpParameter)->hwnd; //获取当前对话框的句柄
SOCKET tcpsock = ((RECVPARAM * )lpParameter)->sock;
char str_toll_package[41912]={0};
//str_toll_package = (char *)malloc(sizeof(char)*41912);
memset(str_toll_package, 0, 41912);
char buf[1352] = "####000000****";
int packet_order = 0; //记录发送的第几帧包
char recvBuf[5000], tempBuf[5000];
int iSend = 0;
while(1)
{
//等待客户请求到来
memset(recvBuf, 0, sizeof(recvBuf)); //初始化字符数组
//接收数据
int recreturnval = recv(tcpsock, recvBuf,1352,0);
if(recreturnval <= 0 && errno != EINTR){
break;
}
memset(tempBuf, 0, sizeof(tempBuf)); //初始化字符数组
//sprintf(tempBuf, "%s", recvBuf);
strncpy(tempBuf, recvBuf, recreturnval);
int flag = 0;
//将数据传给对话框
if(tempBuf[4] == '0' && tempBuf[5] == '0' && tempBuf[0] == '#' && tempBuf[1] == '#' && tempBuf[2] == '#' && tempBuf[3] == '#')
{
flag = 1;
memset(str_toll_package, 0, 41912);
iSend = send(tcpsock, buf, sizeof(buf) ,0);
if(iSend == SOCKET_ERROR){
printf("send failed");
break;
}
//调用参数包解析函数, 存入数据库的参数包表中
unsigned char *packPackage = (unsigned char *)tempBuf;
parameterData((LPARAM)packPackage);
::PostMessage(hwnd, WM_RECVDATA, 0, (LPARAM)packPackage); //主对话框显示数据包和封包数据
}
else
{
if(flag == 1)
{
if(tempBuf[4] == '0' && tempBuf[5] == '2')
{ //进行解包操作
::PostMessage(hwnd, WM_RECVDATA, 0, (LPARAM)tempBuf); //主对话框显示数据包和封包数据
strcat(str_toll_package, tempBuf); //拼包
if(tempBuf[6] == 'F' && tempBuf[7] == 'F')
{
/* int isend1 = send(tcpsock, buf, sizeof(buf) ,0);
if(isend1 == SOCKET_ERROR){
printf("send failed");
break;
}*/
char *resultbuf = packetData((LPARAM)str_toll_package);
int isend2 =send(tcpsock, resultbuf, 1352 ,0);
if(isend2 == SOCKET_ERROR)
{
printf("send failed");
break;
}
::PostMessage(hwnd, WM_RECVDATA, 0, (LPARAM)resultbuf); //主对话框显示数据包和封包数据
flag = 0;
}
}
}
}
}//while
closesocket(tcpsock);//关闭accept创建的套接字
return 0;
}
// 在主对话框中显示数据包和封包数据
LRESULT CServerDlg::OnRecvData(WPARAM wParam, LPARAM lParam)
{
ofstream in;
in.open("packetdata.txt",ios::ate); //ios::trunc表示在打开文件前将文件清空,由于是写入,文件不存在则创建
CString strGetPacket;
CString strTemp;
GetDlgItemText(IDC_LIST_DATA,strTemp);
strGetPacket.Format(_T("%s"), (char *)lParam);
strTemp+=strGetPacket;
strTemp += "\r\n";
in<< strTemp<<"\r"<<"\n";
in.close();//关闭文件
SetDlgItemText(IDC_LIST_DATA,strTemp);
return 0;
}
</pre><p></p><blockquote style="margin:0 0 0 40px; border:none; padding:0px"><p></p><pre name="code" class="cpp">
<span style="font-size:18px;"><strong>主要学习了tcp通信的socket编程和多线程编程。</strong></span>
</pre><p></p><p><span style="background-color: rgb(240, 240, 240);"><span style="font-size:18px;"><strong>本文的程序还存在缺陷:</strong></span></span></p><p><span style="background-color: rgb(240, 240, 240);"><span style="font-size:18px;"><strong>采用并发式服务器可以处理多个客户端的请求。并发式服务器的实现主要有四种方式:</strong></span></span></p><p></p><p></p><p></p><pre name="code" class="cpp"><span style="font-size:18px;"><strong><span style="font-family: Arial, Helvetica, sans-serif;">(1)每个客户端一个线程方式:该方式中服务器对于每个客户端的请求都创建一个新的线程为之服务。有多少个客户端,就创建多少个线程。这样线程会占用大量的系统资源,那么无限制</span><span style="font-family: Arial, Helvetica, sans-serif;">的创建新的线程对于服务器来说是一个非常巨大的挑战。(本文的采用的方式)</span></strong></span>
(2)首先创建固定量线程数方式:服务器在启动时,首先创建一定数量的线程,形成线程池。每当一个客户端请求到来时,从线程池中分配一个空闲线程为之服务,一旦请求完毕,就释放线程,将其放回线程池。当线程池中没有空闲的线程时,客户不得不等待,直到有空闲的线程出现为止。这种方式不用频繁的创建和删除线程,同时在系统中线程的数量是固定的。
(3)逐步创建线程方式:利用这种方式,服务器的线程数有一个上限。有客户到来时,先检查线程池中是否有空闲线程,若有,则直接分配该线程为之服务。如没有,检测线程数是否已经达到上限,若没有达到上限,则创建一个新的线程为之服务。若已经达到上限,则放入等待队列中,知道线程池中有空闲的线程为止。当一个客户请求服务完毕时,将其放入线程池中,供下次使用。
<span style="font-family: Arial, Helvetica, sans-serif;"><span style="font-size:18px;"><strong>(4)单线程方式:使用一个线程服务所有的客户请求,它主要采用的是socket的非阻塞模式,一个线程内可以循环处理多个客户的请求。这样方式无法使用多cpu带来的好处。</strong></span></span>