使用socket写代码主要是要看自己的需求是什么。 如果通信时,内容很重要就要使TCP方式。 如果用户数太多,可能就要使用UDP方式了。 在TCP模式下,最简单的方式就是这样的,使阻塞方式: 服务端: 1.初始化socket环境,创建socket 2.梆定一个端口 3.开始监听 4.接收客户端 5.接收到客户端之后,使用这个socket来与这个客户通信
#include "stdAfx.h" #include <winsock2.h> #include <mswsock.h> #include <iostream>
using namespace std;
#pragma comment(lib, "ws2_32.lib") #pragma comment(lib, "mswsock.lib")
DWORD IniSOCKDLL() { WORD wVersionRequested; WSADATA wsaData; int err=0; wVersionRequested = MAKEWORD( 2, 2 ); err = WSAStartup( wVersionRequested, &wsaData ); return err; }
int main(int argc, char* argv[]) { cout<<"程序开始"<<endl; IniSOCKDLL(); SOCKET ss=WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, NULL); SOCKADDR_IN addr; int len; addr.sin_family=AF_INET; addr.sin_addr.s_addr = htonl(INADDR_ANY); addr.sin_port = htons(1002); len=sizeof(addr); bind(ss , (PSOCKADDR)&addr , len); listen(ss,5); SOCKET sc=accept(ss,(PSOCKADDR)&addr,&len); char buff[1024]; ZeroMemory(buff,1024); recv(sc,buff,1024,0); cout<<buff<<endl; ZeroMemory(buff,1024); memcpy(buff,"123",3); send(sc,buff,3,0); closesocket(sc); closesocket(ss); return 0; }
客户端: 1.初始化socket环境,创建socket 2.连接服务端 3.开启一个线程来接收数据 4.使用send直接发数据包
#include "stdAfx.h" #include <winsock2.h> #include <mswsock.h> #include <iostream>
using namespace std;
#pragma comment(lib, "ws2_32.lib") #pragma comment(lib, "mswsock.lib")
DWORD IniSOCKDLL() { WORD wVersionRequested; WSADATA wsaData; int err=0; wVersionRequested = MAKEWORD( 2, 2 ); err = WSAStartup( wVersionRequested, &wsaData ); return err; }
int main(int argc, char* argv[]) { IniSOCKDLL(); SOCKET sc=WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, NULL); SOCKADDR_IN addr; int len; addr.sin_family=AF_INET; addr.sin_addr.s_addr = inet_addr("127.0.0.1"); addr.sin_port = htons(1002); len=sizeof(addr); connect(sc, (struct sockaddr *)&addr, len); char buff[1024]; ZeroMemory(buff,1024); memcpy(buff,"123",3); send(sc,buff,3,0);
recv(sc,buff,1024,0); cout<<buff<<endl;
closesocket(sc); return 0; }
由这个我们可以做一个这样的模型: 为每个阻塞函数开一个线程,让它来处理。 这就要留意退出的时候把这一些线程给关闭。
当然TCP方式的模型还有事件选择模型。 就是把所有的网络事件和我们的一个程序里定义的事件梆定。 这个有它的好处,可能可以让我们更好的写一个线程来管理 接收与发送。 现在来讲一下一个完成端口模型。
完成端口 一个完成端口其实就是一个通知队列,由操作系统把已经完成的重叠I/O请求的通知 放入其中。当某项I/O操作一旦完成,某个可以对该操作结果进行处理的工作者线程 就会收到一则通知。而套接字在被创建后,可以在任何时候与某个完成端口进行关 联。 步骤: 1、创建一个空的完成端口; 2、得到本地机器的CPU个数; 3、开启CPU*2个工作线程(又名线程池),全部都在等待完成端口的完成包; 4、创建TCP的监听socket,使用事件邦定,创建监听线程; 5、当有人连接进入的时候,将Client socket保存到一个我们自己定义的关键键, 并把它与我们创建的完成端口关联; 6、使用WSARecv和WSASend函数投递一些请求,这是使用重叠I/O的方式; 7、重复5~6;
注:1、重叠I/O的方式中,接收与发送数据包的时候,一定要进行投递请求这是 它们这个体系结构的特点 当然,在完成端口方式中,不是直接使用的WSARecv和WSASend函数进行请求 的投递的。而是使用的ReadFile,Write的方式 2、完成端口使用了系统内部的一些模型,所以我们只要按照一定的顺序调用就 可以完成了。 3、完成端口是使用在这样的情况下,有成千上万的用户连接的时候,它能够 保证性能不会降低。
#include <winsock2.h> #include <windows.h> #include <stdio.h>
#define PORT 5150 #define DATA_BUFSIZE 8192
//关键项 typedef struct { OVERLAPPED Overlapped; WSABUF DataBuf; CHAR Buffer[DATA_BUFSIZE]; DWORD BytesSEND; DWORD BytesRECV; } PER_IO_OPERATION_DATA, * LPPER_IO_OPERATION_DATA;
typedef struct { SOCKET Socket; } PER_HANDLE_DATA, * LPPER_HANDLE_DATA;
DWORD WINAPI ServerWorkerThread(LPVOID CompletionPortID);
void main(void) { SOCKADDR_IN InternetAddr; SOCKET Listen; SOCKET Accept; HANDLE CompletionPort; SYSTEM_INFO SystemInfo; LPPER_HANDLE_DATA PerHandleData; LPPER_IO_OPERATION_DATA PerIoData; int i; DWORD RecvBytes; DWORD Flags; DWORD ThreadID; WSADATA wsaData; DWORD Ret;
if ((Ret = WSAStartup(0x0202, &wsaData)) != 0) { printf("WSAStartup failed with error %d\n", Ret); return; }
//打开一个空的完成端口
if ((CompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0)) == NULL) { printf( "CreateIoCompletionPort failed with error: %d\n", GetLastError()); return; }
// Determine how many processors are on the system.
GetSystemInfo(&SystemInfo);
// 开启cpu个数的2倍个的线程
for(i = 0; i < SystemInfo.dwNumberOfProcessors * 2; i++) { HANDLE ThreadHandle;
// Create a server worker thread and pass the completion port to the thread.
if ((ThreadHandle = CreateThread(NULL, 0, ServerWorkerThread, CompletionPort, 0, &ThreadID)) == NULL) { printf("CreateThread() failed with error %d\n", GetLastError()); return; }
// Close the thread handle CloseHandle(ThreadHandle); }
//打开一个服务器socket
if ((Listen = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED)) == INVALID_SOCKET) { printf("WSASocket() failed with error %d\n", WSAGetLastError()); return; }
InternetAddr.sin_family = AF_INET; InternetAddr.sin_addr.s_addr = htonl(INADDR_ANY); InternetAddr.sin_port = htons(PORT);
if (bind(Listen, (PSOCKADDR) &InternetAddr, sizeof(InternetAddr)) == SOCKET_ERROR) { printf("bind() failed with error %d\n", WSAGetLastError()); return; }
if (listen(Listen, 5) == SOCKET_ERROR) { printf("listen() failed with error %d\n", WSAGetLastError()); return; }
//开始接收从客户端来的连接
while(TRUE) { if ((Accept = WSAAccept(Listen, NULL, NULL, NULL, 0)) == SOCKET_ERROR) { printf("WSAAccept() failed with error %d\n", WSAGetLastError()); return; }
// 创建一个关键项用于保存这个客户端的信息,用户接收发送的重叠结构, // 还有使用到的缓冲区 if ((PerHandleData = (LPPER_HANDLE_DATA) GlobalAlloc(GPTR, sizeof(PER_HANDLE_DATA))) == NULL) { printf("GlobalAlloc() failed with error %d\n", GetLastError()); return; }
// Associate the accepted socket with the original completion port.
printf("Socket number %d connected\n", Accept); PerHandleData->Socket = Accept;
//与我们的创建的那个完成端口关联起来,将关键项也与指定的一个完成端口关联 if (CreateIoCompletionPort((HANDLE) Accept, CompletionPort, (DWORD) PerHandleData, 0) == NULL) { printf("CreateIoCompletionPort failed with error %d\n", GetLastError()); return; }
// 投递一次接收,由于接收都需要使用这个函数来投递一个接收的准备
if ((PerIoData = (LPPER_IO_OPERATION_DATA) GlobalAlloc(GPTR, sizeof(PER_IO_OPERATION_DATA))) == NULL) { printf("GlobalAlloc() failed with error %d\n", GetLastError()); return; }
ZeroMemory(&(PerIoData->Overlapped), sizeof(OVERLAPPED)); PerIoData->BytesSEND = 0; PerIoData->BytesRECV = 0; PerIoData->DataBuf.len = DATA_BUFSIZE; PerIoData->DataBuf.buf = PerIoData->Buffer;
Flags = 0; if (WSARecv(Accept, &(PerIoData->DataBuf), 1, &RecvBytes, &Flags, &(PerIoData->Overlapped), NULL) == SOCKET_ERROR) { if (WSAGetLastError() != ERROR_IO_PENDING) { printf("WSARecv() failed with error %d\n", WSAGetLastError()); return; } } } } //工作线程 DWORD WINAPI ServerWorkerThread(LPVOID CompletionPortID) { HANDLE CompletionPort = (HANDLE) CompletionPortID; DWORD BytesTransferred; LPOVERLAPPED Overlapped; LPPER_HANDLE_DATA PerHandleData; LPPER_IO_OPERATION_DATA PerIoData; DWORD SendBytes, RecvBytes; DWORD Flags; while(TRUE) { //完成端口有消息来了 if (GetQueuedCompletionStatus(CompletionPort, &BytesTransferred, (LPDWORD)&PerHandleData, (LPOVERLAPPED *) &PerIoData, INFINITE) == 0) { printf("GetQueuedCompletionStatus failed with error %d\n", GetLastError()); return 0; }
//是不是有人退出了
if (BytesTransferred == 0) { printf("Closing socket %d\n", PerHandleData->Socket);
if (closesocket(PerHandleData->Socket) == SOCKET_ERROR) { printf("closesocket() failed with error %d\n", WSAGetLastError()); return 0; }
GlobalFree(PerHandleData); GlobalFree(PerIoData); continue; }
//
if (PerIoData->BytesRECV == 0) { PerIoData->BytesRECV = BytesTransferred; PerIoData->BytesSEND = 0; } else { PerIoData->BytesSEND += BytesTransferred; }
if (PerIoData->BytesRECV > PerIoData->BytesSEND) {
// Post another WSASend() request. // Since WSASend() is not gauranteed to send all of the bytes requested, // continue posting WSASend() calls until all received bytes are sent.
ZeroMemory(&(PerIoData->Overlapped), sizeof(OVERLAPPED));
PerIoData->DataBuf.buf = PerIoData->Buffer + PerIoData->BytesSEND; PerIoData->DataBuf.len = PerIoData->BytesRECV - PerIoData->BytesSEND;
if (WSASend(PerHandleData->Socket, &(PerIoData->DataBuf), 1, &SendBytes, 0, &(PerIoData->Overlapped), NULL) == SOCKET_ERROR) { if (WSAGetLastError() != ERROR_IO_PENDING) { printf("WSASend() failed with error %d\n", WSAGetLastError()); return 0; } } } else { PerIoData->BytesRECV = 0;
// Now that there are no more bytes to send post another WSARecv() request.
Flags = 0; ZeroMemory(&(PerIoData->Overlapped), sizeof(OVERLAPPED));
PerIoData->DataBuf.len = DATA_BUFSIZE; PerIoData->DataBuf.buf = PerIoData->Buffer;
if (WSARecv(PerHandleData->Socket, &(PerIoData->DataBuf), 1, &RecvBytes, &Flags, &(PerIoData->Overlapped), NULL) == SOCKET_ERROR) { if (WSAGetLastError() != ERROR_IO_PENDING) { printf("WSARecv() failed with error %d\n", WSAGetLastError()); return 0; } } } } }