公司安排开始做服务器,没有接触过这些东西。从零开始学习。
项目是做个聊天系统客户端用flex,服务器用C++,让我先出Demo。
服务器部分参照Windows网络编程一书中的select例子,基本一样,只是把例子中的的单个回应消息改成了群发消息。
虽然我对select模型还不熟悉,但是我知道如果要在一个套接字上发消息就必须设置FD_SET WriteSet,如果要在一个套接字上读消息就必须设置FD_SET ReadSet,设置就是调用函数FD_SET(SOCKET S, FD_SET* Set)。
设置这个结构的目的相当于设置一张socket的列表,调用select函数,系统就会管理这个列表里面的socket。
接收消息的过程是循环遍历所有端口,如果有端口有数据接收进来而且这个端口存在于读的列表中,则去读取数据。
如果有端口要发送数据,则先看看这个端口是不是在写列表里,如果在则发送数据,否则不发。
select模式有端口数限制,FD_SIZE被定义为64,如果不去修改这个系统定义的宏,这FD_SET结构里面最多只能存64个端口。
我把它改成1000试过,可以接收1000个端口,不知道这样子会不会有什么问题。
经理的意见是改成完成端口。
我又按照书上的完成端口示例弄出一个完成端口的服务器来,
我理解的完成端口就是把监听端口接收进来的客户端套接字绑定到一个完成端口上并开始接收消息,这个完成端口通过重叠IO机制来通知是否有消息到达。
完成端口在底层做的事情应该类似于select模型就是遍历所有和他绑在一起的端口。不知道我的理解对不对,恳请各位看到的指教。
我似懂非懂的看了几章感觉有用的东西 不过我感觉 这两本书是应该需要好好看完的,不会的东西太多了
另外的一些体会:
消息要先弄成消息队列,一个线程专门处理消息,一个线程专门处理接收数据,这样能保持处理消息的过程同步。
第一次搞服务器于是就第一次搞了多线程。线程同步比较重要,我看了Win32 多线程程序设计的几章帮助很大,很实用,一下子就解决问题了。我觉得这个水很深,继续摸中。
对我有帮助的资料:
windows 网络编程 8章 Winsock I/O方法
Win32 多线程程序设计 4章 同步控制 6章 overlapped I/O 在你身后变戏法
http://www.100ksw.com/jsj/dj/2/C++/3/187681.shtml(简单的socket例子)
http://tongjian.javaeye.com/blog/367252(字节序问题)
我把书上的代码贴贴上来好了,省得以后到处找。
select模型
- // Module Name: select.cpp
- //
- // Description:
- //
- // This sample illustrates how to develop a simple echo server Winsock
- // application using the select() API I/O model. This sample is
- // implemented as a console-style application and simply prints
- // messages when connections are established and removed from the server.
- // The application listens for TCP connections on port 5150 and accepts
- // them as they arrive. When this application receives data from a client,
- // it simply echos (this is why we call it an echo server) the data back in
- // it's original form until the client closes the connection.
- //
- // Compile:
- //
- // cl -o select select.cpp ws2_32.lib
- //
- // Command Line Options:
- //
- // select.exe
- //
- // Note: There are no command line options for this sample.
- //
- #include <winsock2.h>
- #include <windows.h>
- #include <stdio.h>
- #define PORT 5150
- #define DATA_BUFSIZE 8192
- typedef struct _SOCKET_INFORMATION {
- CHAR Buffer[DATA_BUFSIZE];
- WSABUF DataBuf;
- SOCKET Socket;
- OVERLAPPED Overlapped;
- DWORD BytesSEND;
- DWORD BytesRECV;
- } SOCKET_INFORMATION, * LPSOCKET_INFORMATION;
- BOOL CreateSocketInformation(SOCKET s);
- void FreeSocketInformation(DWORD Index);
- DWORD TotalSockets = 0;
- LPSOCKET_INFORMATION SocketArray[FD_SETSIZE];
- void main(void)
- {
- SOCKET ListenSocket;
- SOCKET AcceptSocket;
- SOCKADDR_IN InternetAddr;
- WSADATA wsaData;
- INT Ret;
- FD_SET WriteSet;
- FD_SET ReadSet;
- DWORD i;
- DWORD Total;
- ULONG NonBlock;
- DWORD Flags;
- DWORD SendBytes;
- DWORD RecvBytes;
- if ((Ret = WSAStartup(0x0202,&wsaData)) != 0)
- {
- printf("WSAStartup() failed with error %d/n", Ret);
- WSACleanup();
- return;
- }
- // Prepare a socket to listen for connections.
- if ((ListenSocket = 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(ListenSocket, (PSOCKADDR) &InternetAddr, sizeof(InternetAddr))
- == SOCKET_ERROR)
- {
- printf("bind() failed with error %d/n", WSAGetLastError());
- return;
- }
- if (listen(ListenSocket, 5))
- {
- printf("listen() failed with error %d/n", WSAGetLastError());
- return;
- }
- // Change the socket mode on the listening socket from blocking to
- // non-block so the application will not block waiting for requests.
- NonBlock = 1;
- if (ioctlsocket(ListenSocket, FIONBIO, &NonBlock) == SOCKET_ERROR)
- {
- printf("ioctlsocket() failed with error %d/n", WSAGetLastError());
- return;
- }
- while(TRUE)
- {
- // Prepare the Read and Write socket sets for network I/O notification.
- FD_ZERO(&ReadSet);
- FD_ZERO(&WriteSet);
- // Always look for connection attempts.
- FD_SET(ListenSocket, &ReadSet);
- // Set Read and Write notification for each socket based on the
- // current state the buffer. If there is data remaining in the
- // buffer then set the Write set otherwise the Read set.
- for (i = 0; i < TotalSockets; i++)
- if (SocketArray[i]->BytesRECV > SocketArray[i]->BytesSEND)
- FD_SET(SocketArray[i]->Socket, &WriteSet);
- else
- FD_SET(SocketArray[i]->Socket, &ReadSet);
- if ((Total = select(0, &ReadSet, &WriteSet, NULL, NULL)) == SOCKET_ERROR)
- {
- printf("select() returned with error %d/n", WSAGetLastError());
- return;
- }
- // Check for arriving connections on the listening socket.
- if (FD_ISSET(ListenSocket, &ReadSet))
- {
- Total--;
- if ((AcceptSocket = accept(ListenSocket, NULL, NULL)) != INVALID_SOCKET)
- {
- // Set the accepted socket to non-blocking mode so the server will
- // not get caught in a blocked condition on WSASends
- NonBlock = 1;
- if (ioctlsocket(AcceptSocket, FIONBIO, &NonBlock) == SOCKET_ERROR)
- {
- printf("ioctlsocket() failed with error %d/n", WSAGetLastError());
- return;
- }
- if (CreateSocketInformation(AcceptSocket) == FALSE)
- return;
- }
- else
- {
- if (WSAGetLastError() != WSAEWOULDBLOCK)
- {
- printf("accept() failed with error %d/n", WSAGetLastError());
- return;
- }
- }
- }
- // Check each socket for Read and Write notification until the number
- // of sockets in Total is satisfied.
- for (i = 0; Total > 0 && i < TotalSockets; i++)
- {
- LPSOCKET_INFORMATION SocketInfo = SocketArray[i];
- // If the ReadSet is marked for this socket then this means data
- // is available to be read on the socket.
- if (FD_ISSET(SocketInfo->Socket, &ReadSet))
- {
- Total--;
- SocketInfo->DataBuf.buf = SocketInfo->Buffer;
- SocketInfo->DataBuf.len = DATA_BUFSIZE;
- Flags = 0;
- if (WSARecv(SocketInfo->Socket, &(SocketInfo->DataBuf), 1, &RecvBytes,
- &Flags, NULL, NULL) == SOCKET_ERROR)
- {
- if (WSAGetLastError() != WSAEWOULDBLOCK)
- {
- printf("WSARecv() failed with error %d/n", WSAGetLastError());
- FreeSocketInformation(i);
- }
- continue;
- }
- else
- {
- SocketInfo->BytesRECV = RecvBytes;
- // If zero bytes are received, this indicates the peer closed the
- // connection.
- if (RecvBytes == 0)
- {
- FreeSocketInformation(i);
- continue;
- }
- }
- }
- // If the WriteSet is marked on this socket then this means the internal
- // data buffers are available for more data.
- if (FD_ISSET(SocketInfo->Socket, &WriteSet))
- {
- Total--;
- SocketInfo->DataBuf.buf = SocketInfo->Buffer + SocketInfo->BytesSEND;
- SocketInfo->DataBuf.len = SocketInfo->BytesRECV - SocketInfo->BytesSEND;
- if (WSASend(SocketInfo->Socket, &(SocketInfo->DataBuf), 1, &SendBytes, 0,
- NULL, NULL) == SOCKET_ERROR)
- {
- if (WSAGetLastError() != WSAEWOULDBLOCK)
- {
- printf("WSASend() failed with error %d/n", WSAGetLastError());
- FreeSocketInformation(i);
- }
- continue;
- }
- else
- {
- SocketInfo->BytesSEND += SendBytes;
- if (SocketInfo->BytesSEND == SocketInfo->BytesRECV)
- {
- SocketInfo->BytesSEND = 0;
- SocketInfo->BytesRECV = 0;
- }
- }
- }
- }
- }
- }
- BOOL CreateSocketInformation(SOCKET s)
- {
- LPSOCKET_INFORMATION SI;
- printf("Accepted socket number %d/n", s);
- if ((SI = (LPSOCKET_INFORMATION) GlobalAlloc(GPTR,
- sizeof(SOCKET_INFORMATION))) == NULL)
- {
- printf("GlobalAlloc() failed with error %d/n", GetLastError());
- return FALSE;
- }
- // Prepare SocketInfo structure for use.
- SI->Socket = s;
- SI->BytesSEND = 0;
- SI->BytesRECV = 0;
- SocketArray[TotalSockets] = SI;
- TotalSockets++;
- return(TRUE);
- }
- void FreeSocketInformation(DWORD Index)
- {
- LPSOCKET_INFORMATION SI = SocketArray[Index];
- DWORD i;
- closesocket(SI->Socket);
- printf("Closing socket number %d/n", SI->Socket);
- GlobalFree(SI);
- // Squash the socket array
- for (i = Index; i < TotalSockets; i++)
- {
- SocketArray[i] = SocketArray[i + 1];
- }
- TotalSockets--;
- }
完成端口模型
- // Module Name: iocmplt.cpp
- //
- // Description:
- //
- // This sample illustrates how to develop a simple echo server Winsock
- // application using the completeion port I/O model. This
- // sample is implemented as a console-style application and simply prints
- // messages when connections are established and removed from the server.
- // The application listens for TCP connections on port 5150 and accepts them
- // as they arrive. When this application receives data from a client, it
- // simply echos (this is why we call it an echo server) the data back in
- // it's original form until the client closes the connection.
- //
- // Compile:
- //
- // cl -o iocmplt iocmplt.cpp ws2_32.lib
- //
- // Command Line Options:
- //
- // iocmplt.exe
- //
- // Note: There are no command line options for this sample.
- #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;
- }
- // Setup an I/O completion port.
- 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);
- // Create worker threads based on the number of processors available on the
- // system. Create two worker threads for each processor.
- 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);
- }
- // Create a listening 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;
- }
- // Prepare socket for listening
- if (listen(Listen, 5) == SOCKET_ERROR)
- {
- printf("listen() failed with error %d/n", WSAGetLastError());
- return;
- }
- // Accept connections and assign to the completion port.
- while(TRUE)
- {
- if ((Accept = WSAAccept(Listen, NULL, NULL, NULL, 0)) == SOCKET_ERROR)
- {
- printf("WSAAccept() failed with error %d/n", WSAGetLastError());
- return;
- }
- // Create a socket information structure to associate with the socket
- 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;
- }
- // Create per I/O socket information structure to associate with the
- // WSARecv call below.
- 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;
- }
- // First check to see if an error has occured on the socket and if so
- // then close the socket and cleanup the SOCKET_INFORMATION structure
- // associated with the socket.
- 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;
- }
- // Check to see if the BytesRECV field equals zero. If this is so, then
- // this means a WSARecv call just completed so update the BytesRECV field
- // with the BytesTransferred value from the completed WSARecv() call.
- 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;
- }
- }
- }
- }
- }
从flex过来的数据中取int
- void memcpyHtol(int &des, char* &str)
- {
- int len = sizeof(des);
- memcpy(&des, str, len);
- des = htonl(des);
- str += len;
- }
//---------------------------------------------------分割线-----------------------------------------//
flex客户端代码
chat.mxml:
- <?xml version="1.0" encoding="utf-8"?>
- <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" width="388" height="376" applicationComplete="init()">
- <mx:Script>
- <!--[CDATA[
- import flash.net.Socket;
- import flash.utils.ByteArray;
- private var socket:Socket=new Socket();
- private var strInfor:String = "";
- private var m_name:String = "";
- private var m_msg:String = "";
- internal function init():void
- {
- m_textarea.text = "";
- }
- internal function onLogin():void
- {
- socket.connect("127.0.0.1",int(m_port_input.text));//执行连接
- socket.addEventListener(Event.CONNECT,funConnect);
- socket.addEventListener(Event.CLOSE,funClose);
- socket.addEventListener(ProgressEvent.SOCKET_DATA,funSocket);
- }
- internal function onSend():void
- {
- m_msg = m_text_input.text;
- socket.writeMultiByte(m_msg, "utf-8");
- socket.flush();
- }
- internal function funConnect(event:Event):void
- {
- funUpdateText("连接成功!");
- m_text_input.enabled = true;
- send_btn.enabled = true;
- }
- internal function funClose(event:Event):void
- {
- funUpdateText("连接关闭!");
- }
- internal function funSocket(event:ProgressEvent):void
- {
- var msg:String="";
- while(socket.bytesAvailable)
- {
- msg+=socket.readMultiByte(socket.bytesAvailable,"utf8");
- var arr:Array=msg.split('n');
- for(var i:int=0;i<arr.length;i++)
- {
- if(arr[i].length>1)
- {
- var myPattern:RegExp=/r/;
- arr[i]=arr[i].replace(myPattern,'');
- funUpdateText(arr[i]);
- }
- }
- }
- m_text_input.text = "";
- }
- internal function funUpdateText(str:String):void
- {
- strInfor += str + "/n";
- m_textarea.text = strInfor;
- m_textaream_textarea.verticalScrollPosition = m_textarea.maxVerticalScrollPosition;//滚动到最下面
- }
- internal function ClearScreen():void
- {
- strInfor = "";
- m_textarea.text = "";
- }
- ]]-->
- </mx:Script>
- <mx:Panel x="28.5" y="24" width="233" height="79" layout="absolute" title="登录窗口" visible="false">
- <mx:Label text="用户名" width="38" x="10" y="10"/>
- <mx:TextInput id="m_name_input" width="97" x="52" y="8"/>
- </mx:Panel>
- <mx:Panel x="28.5" y="40" width="331" height="312" layout="absolute" title="聊天测试">
- <mx:TextArea x="10" y="10" height="227" width="290" id="m_textarea" editable="false"/>
- <mx:TextInput x="10" y="245" width="233" id="m_text_input" enabled="false"/>
- <mx:Button x="253" y="245" label="发送" click="onSend()" enabled="false" id="send_btn"/>
- </mx:Panel>
- <mx:Button label="登录" id="loginbtn" click="onLogin()" x="122.5" y="10"/>
- <mx:TextInput x="66.5" y="10" width="48" id="m_port_input" text="1500"/>
- <mx:Button x="294" y="10" label="清屏" click="ClearScreen()"/>
- <mx:Label x="28.5" y="12" text="端口号"/>
- </mx:Application>
完毕!