server:
// TcpServer.cpp : 定義控制台應用程序的入口點。
//
#include "stdafx.h"
#include
#include
#pragma comment(lib,"WS2_32.lib")
#define PORT 9990
#define DATA_BUFSIZE 8192
// 定義套接字信息
typedef struct _SOCKET_INFORMATION {
CHAR Buffer[DATA_BUFSIZE]; // 發送和接收數據的緩沖區
WSABUF DataBuf; // 定義發送和接收數據緩沖區的結構體,包括緩沖區的長度和內容
SOCKET Socket; // 與客戶端進行通信的套接字
DWORD BytesSEND; // 保存套接字發送的字節數
DWORD BytesRECV; // 保存套接字接收的字節數
} SOCKET_INFORMATION, * LPSOCKET_INFORMATION;
DWORD TotalSockets = 0; // 記錄正在使用的套接字總數量
LPSOCKET_INFORMATION SocketArray[FD_SETSIZE]; // 保存Socket信息對象的數組,FD_SETSIZE表示SELECT模型中允許的最大套接字數量
// 創建SOCKET信息
BOOL CreateSocketInformation(SOCKET s)
{
LPSOCKET_INFORMATION SI; // 用於保存套接字的信息
// printf("Accepted socket number %d\n", s); // 打開已接受的套接字編號
// 為SI分配內存空間
if ((SI = (LPSOCKET_INFORMATION) GlobalAlloc(GPTR,
sizeof(SOCKET_INFORMATION))) == NULL)
{
printf("GlobalAlloc() failed with error %d\n", GetLastError());
return FALSE;
}
// 初始化SI的值
SI->Socket = s;
SI->BytesSEND = 0;
SI->BytesRECV = 0;
// 在SocketArray數組中增加一個新元素,用於保存SI對象
SocketArray[TotalSockets] = SI;
TotalSockets++; // 增加套接字數量
return(TRUE);
}
// 從數組SocketArray中刪除指定的LPSOCKET_INFORMATION對象
void FreeSocketInformation(DWORD Index)
{
LPSOCKET_INFORMATION SI = SocketArray[Index]; // 獲取指定索引對應的LPSOCKET_INFORMATION對象
DWORD i;
// 關閉套接字
closesocket(SI->Socket);
//printf("Closing socket number %d\n", SI->Socket);
// 釋放指定LPSOCKET_INFORMATION對象資源
GlobalFree(SI);
// 將數組中index索引後面的元素前移
for (i = Index; i < TotalSockets; i++)
{
SocketArray[i] = SocketArray[i+1];
}
TotalSockets--; // 套接字總數減1
}
// 主函數,啟動服務器
int _tmain(int argc, _TCHAR* argv[])
{
SOCKET ListenSocket; // 監聽套接字
SOCKET AcceptSocket; // 與客戶端進行通信的套接字
SOCKADDR_IN InternetAddr; // 服務器的地址
WSADATA wsaData; // 用於初始化套接字環境
INT Ret; // WinSock API的返回值
FD_SET WriteSet; // 獲取可寫性的套接字集合
FD_SET ReadSet; // 獲取可讀性的套接字集合
DWORD Total = 0; // 處於就緒狀態的套接字數量
DWORD SendBytes; // 發送的字節數
DWORD RecvBytes; // 接收的字節數
// 初始化WinSock環境
if ((Ret = WSAStartup(0x0202,&wsaData)) != 0)
{
printf("WSAStartup() failed with error %d\n", Ret);
WSACleanup();
return -1;
}
// 創建用於監聽的套接字
if ((ListenSocket = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0,
WSA_FLAG_OVERLAPPED)) == INVALID_SOCKET)
{
printf("WSASocket() failed with error %d\n", WSAGetLastError());
return -1;
}
// 設置監聽地址和端口號
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 -1;
}
// 開始監聽
if (listen(ListenSocket, 5))
{
printf("listen() failed with error %d\n", WSAGetLastError());
return -1;
}
/* // 設置為非阻塞模式
ULONG NonBlock = 1;
if(ioctlsocket(ListenSocket, FIONBIO, &NonBlock) == SOCKET_ERROR)
{
printf("ioctlsocket() failed with error %d\n", WSAGetLastError());
return -1;
}
// 為ListenSocket套接字創建對應的SOCKET_INFORMATION
// 這樣就可以把ListenSocket添加到SocketArray數組中
*/
CreateSocketInformation(ListenSocket);
while(TRUE)
{
// 准備用於網絡I/O通知的讀/寫套接字集合
FD_ZERO(&ReadSet);
FD_ZERO(&WriteSet);
// 向ReadSet集合中添加監聽套接字ListenSocket
FD_SET(ListenSocket, &ReadSet);
// 將SocketArray數組中的所有套接字添加到WriteSet和ReadSet集合中
// SocketArray數組中保存著監聽套接字和所有與客戶端進行通信的套接字
// 這樣就可以使用select()判斷哪個套接字有接入數據或者讀取/寫入數據
for (DWORD i=0; iSocket, &WriteSet);
FD_SET(SocketInfo->Socket, &ReadSet);
}
// 判斷讀/寫套接字集合中就緒的套接字
if((Total = select(0, &ReadSet, &WriteSet, NULL, NULL)) == SOCKET_ERROR)
{
printf("select() returned with error %d\n", WSAGetLastError());
return -1;
}
// 依次處理所有套接字。本服務器是一個回應服務器,即將從客戶端收到的字符串再發回到客戶端。
for (DWORD i=0; iSocket, &ReadSet))
{
if(SocketInfo->Socket == ListenSocket) // 對於監聽套接字來說,可讀表示有新的連接請求
{
Total--; // 就緒的套接字減1
// 接受連接請求,得到與客戶端進行通信的套接字AcceptSocket
if((AcceptSocket = accept(ListenSocket, NULL, NULL)) != INVALID_SOCKET)
{
printf("連接成功!\n");
/*
// 設置套接字AcceptSocket為非阻塞模式
// 這樣服務器在調用WSASend()函數發送數據時就不會被阻塞
NonBlock = 1;
if(ioctlsocket(AcceptSocket, FIONBIO, &NonBlock) == SOCKET_ERROR)
{
printf("ioctlsocket() failed with error %d\n", WSAGetLastError());
return -1;
}
*/
// 創建套接字信息,初始化LPSOCKET_INFORMATION結構體數據,將AcceptSocket添加到SocketArray數組中
if(CreateSocketInformation(AcceptSocket) == FALSE)
return -1;
}
else
{
if(WSAGetLastError() != WSAEWOULDBLOCK)
{
printf("accept() failed with error %d\n", WSAGetLastError());
return -1;
}
}
}
else // 接收數據
{
// 如果當前套接字在ReadSet集合中,則表明該套接字上有可以讀取的數據
if (FD_ISSET(SocketInfo->Socket, &ReadSet))
{
Total--; // 減少一個處於就緒狀態的套接字
memset(SocketInfo->Buffer, ' ', DATA_BUFSIZE);
ZeroMemory(SocketInfo->DataBuf.buf,DATA_BUFSIZE);// 初始化緩沖區
SocketInfo->DataBuf.buf = SocketInfo->Buffer; // 初始化緩沖區位置
SocketInfo->DataBuf.len = DATA_BUFSIZE; // 初始化緩沖區長度
// 接收數據
DWORD Flags = 0;
if(WSARecv(SocketInfo->Socket, &(SocketInfo->DataBuf), 1, &RecvBytes, &Flags,
NULL, NULL) == SOCKET_ERROR)
{
// 錯誤編碼等於WSAEWOULDBLOCK表示暫沒有數據,否則表示出現異常
if(WSAGetLastError() != WSAEWOULDBLOCK)
{
printf("WSARecv() failed with error %d\n", WSAGetLastError());
FreeSocketInformation(i); // 釋放套接字信息
}
continue;
}
else // 接收數據
{
SocketInfo->BytesRECV = RecvBytes; // 記錄接收數據的字節數
if(RecvBytes == 0) // 如果接收到0個字節,則表示對方關閉連接
{
FreeSocketInformation(i);
continue;
}
else // 如果成功接收數據,則打印收到的數據
{
printf(SocketInfo->DataBuf.buf);
printf("\n");
//
}
}
}
}
}
else
{
// 如果當前套接字在WriteSet集合中,則表明該套接字的內部數據緩沖區中有數據可以發送
if(FD_ISSET(SocketInfo->Socket, &WriteSet))
{
Total--; // 減少一個處於就緒狀態的套接字
SocketInfo->DataBuf.buf = SocketInfo->Buffer + SocketInfo->BytesSEND; // 初始化緩沖區位置
SocketInfo->DataBuf.len = SocketInfo->BytesRECV - SocketInfo->BytesSEND; // 初始化緩沖區長度
if(SocketInfo->DataBuf.len > 0) // 如果有需要發送的數據,則發送數據
{
if(WSASend(SocketInfo->Socket, &(SocketInfo->DataBuf), 1, &SendBytes, 0,
NULL, NULL) == SOCKET_ERROR)
{
// 錯誤編碼等於WSAEWOULDBLOCK表示暫沒有數據,否則表示出現異常
if(WSAGetLastError() != WSAEWOULDBLOCK)
{
printf("WSASend() failed with error %d\n", WSAGetLastError());
FreeSocketInformation(i); // 釋放套接字信息
}
continue;
}
else
{
SocketInfo->BytesSEND += SendBytes; // 記錄發送數據的字節數
// 如果從客戶端接收到的數據都已經發回到客戶端,則將發送和接收的字節數量設置為0
if (SocketInfo->BytesSEND == SocketInfo->BytesRECV)
{
SocketInfo->BytesSEND = 0;
SocketInfo->BytesRECV = 0;
}
}
}
}
} // 如果ListenSocket未就緒,並且返回的錯誤不是WSAEWOULDBLOCK(該錯誤表示沒有接收的連接請求),則出現異常
}
}
// 暫停,按任意鍵退出
system("pause");
return 0;
}
client:
// TcpClient.cpp : 定義控制台應用程序的入口點。
//
#include "stdafx.h"
#include
#include
#include
#pragma comment(lib,"WS2_32.lib")
#define BUF_SIZE 64 // 緩沖區大小
int _tmain(int argc, _TCHAR* argv[])
{
WSADATA wsd; // 用於初始化Windows Socket
SOCKET sHost; // 與服務器進行通信的套接字
SOCKADDR_IN servAddr; // 服務器地址
char buf[BUF_SIZE]; // 用於接受數據緩沖區
int retVal; // 調用各種Socket函數的返回值
// 初始化Windows Socket
if(WSAStartup(MAKEWORD(2,2),&wsd) != 0)
{
printf("WSAStartup failed !\n");
return 1;
}
// 創建套接字
sHost = socket(AF_INET,SOCK_STREAM,IPPROTO_IP);
if(INVALID_SOCKET == sHost)
{
printf("socket failed !\n");
WSACleanup();
return -1;
}
// 設置服務器地址
servAddr.sin_family = AF_INET;
servAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); // 用戶需要根據實際情況修改
servAddr.sin_port = htons(9990); // 在實際應用中,建議將服務器的IP地址和端口號保存在配置文件中
int sServerAddlen = sizeof(servAddr); // 計算地址的長度
// 循環等待
while(true)
{
// 連接服務器
Sleep( 200 );
retVal = connect(sHost,(LPSOCKADDR)&servAddr,sizeof(servAddr));
Sleep( 200 );
if(SOCKET_ERROR == retVal)
{
int err = WSAGetLastError();
if(err == WSAEWOULDBLOCK || err == WSAEINVAL) // 無法立即完成非阻塞套接字上的操作
{
//Sleep(500);
continue;
}
else if(err == WSAEISCONN) // 已建立連接
{
break;
}
else
{
continue;
//printf("connect failed !\n");
//closesocket(sHost);
//WSACleanup();
//return -1;
}
}
}
// 循環向服務器發送字符串,並顯示反饋信息。
// 發送quit將使服務器程序退出,同時客戶端程序自身也將退出
while(true)
{
// 向服務器發送數據
printf("Please input a string to send: ");
// 接收輸入的數據
std::string str;
std::getline(std::cin, str);
// 將用戶輸入的數據復制到buf中
ZeroMemory(buf,BUF_SIZE);
ZeroMemory(buf,BUF_SIZE);
strcpy(buf,str.c_str());
// 循環等待
while(true)
{
// 向服務器發送數據
retVal = send(sHost,buf,strlen(buf),0);
if(SOCKET_ERROR == retVal)
{
int err = WSAGetLastError();
if(err == WSAEWOULDBLOCK) // 無法立即完成非阻塞套接字上的操作
{
continue;
}
else
{
printf("send failed !\n");
closesocket(sHost);
WSACleanup();
return -1;
}
}
break;
}
while(true)
{
ZeroMemory(buf,BUF_SIZE); // 清空接收數據的緩沖區
retVal = recv(sHost,buf,sizeof(buf)+1,0); // 接收服務器回傳的數據
if(SOCKET_ERROR == retVal)
{
int err = WSAGetLastError(); // 獲取錯誤編碼
if(err == WSAEWOULDBLOCK) // 接收數據緩沖區暫無數據
{
Sleep(100);
printf("waiting back msg !\n");
continue;
}
else if(err == WSAETIMEDOUT || err == WSAENETDOWN)
{
printf("recv failed !\n");
closesocket(sHost);
WSACleanup();
return -1;
}
break;
}
break;
}
//ZeroMemory(buf,BUF_SIZE); // 清空接收數據的緩沖區
//retVal = recv(sHost,buf,sizeof(buf)+1,0); // 接收服務器回傳的數據
printf("Recv From Server: %s\n",buf);
// 如果收到quit,則退出
if(strcmp(buf, "quit") == 0)
{
printf("quit!\n");
break;
}
}
// 釋放資源
closesocket(sHost);
WSACleanup();
// 暫停,按任意鍵繼續
system("pause");
return 0;
}