socket通信流程
tcp
server side:
socket()->bind()->listen()->accept()->send()/recv()->closesocket()
client:
socket()->connet()->send()/recv()->closesocket()
udp
server:
socket()->bind()->sendto()/recvfrom()->closesocket()
client:
socket()->sendto()/recvfrom()/closesocket()
winsock
库文件
tcp server
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN //此宏用于预防 winsock.h 和winsock2.h 冲突
#endif
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdio.h>
#pragma comment(lib, "Ws2_32.lib")
tcpclient
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdlib.h>
#include <stdio.h>
// Need to link with Ws2_32.lib, Mswsock.lib, and Advapi32.lib
#pragma comment (lib, "Ws2_32.lib")
#pragma comment (lib, "Mswsock.lib")
#pragma comment (lib, "AdvApi32.lib")
初始化winsock
使用winsock 编程前需要对winsock初始化,使用完毕后需要释放。
winsock初始化函数
int WSAStartup(WORD wVersionRequire,LPWSADATA lpWSAData);
/*
wVersionRequire 是winsock 初始换版本号 ,winsock有多个版本 常用 2.2
lpWSAData 是一个指向WSAdata 的结构体指针
函数返回值 0 成功 ,其他值 失败
*/
winsock 释放函数
int WSACleanup(void);
示例代码
int iResult;
//创建WSADATA 对象
WSADATA wsaData;
//...
// 初始化winsock
iResult = WSAStartup(MAKEWORD(2,2),&wsaData);
if(iResult != 0){
printf("WSAStart failed : %d",iResult);
return 1;
}
TCP
server
创建addrinfo
示例代码
#define DEFAULT_PORT "27015"
struct addrinfo *result = NULL, *ptr = NULL, hints;
ZeroMemory(&hints, sizeof (hints));
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
hints.ai_flags = AI_PASSIVE;
// Resolve the local address and port to be used by the server
iResult = getaddrinfo(NULL, DEFAULT_PORT, &hints, &result);
if (iResult != 0) {
printf("getaddrinfo failed: %d\n", iResult);
WSACleanup();
return 1;
}
socket创建与关闭
scoket(),closesocket()
SOCKET socket(int af,int type,int protocol);
// af代表地址簇,
//type 为套接字类型 stream , dgram,raw 代表流套接字,数据包套接字,原始协议接口
//protocol 协议类型 tcp,udp,icmp ,etc
int closesocket(SOCKET s);
示例代码
SOCKET ListenSocket = INVALID_SOCKET;
// Create a SOCKET for the server to listen for client connections
ListenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
//检查是否有错
if (ListenSocket == INVALID_SOCKET) {
printf("Error at socket(): %ld\n", WSAGetLastError());
freeaddrinfo(result);
WSACleanup();
return 1;
}
绑定socket
// Setup the TCP listening socket
iResult = bind( ListenSocket, result->ai_addr, (int)result->ai_addrlen);
if (iResult == SOCKET_ERROR) {
printf("bind failed with error: %d\n", WSAGetLastError());
freeaddrinfo(result);
closesocket(ListenSocket);
WSACleanup();
return 1;
}
侦听套接字
if ( listen( ListenSocket, SOMAXCONN ) == SOCKET_ERROR ) { //SOMAXCONN 最大连接数
printf( "Listen failed with error: %ld\n", WSAGetLastError() );
freeaddrinfo(result);
closesocket(ListenSocket);
WSACleanup();
return 1;
}
接受连接
创建ClientSocket 临时socket对象,接受客户端的连接
有几种不同的编程技术使用 Winsock,可用于侦听多个客户端连接。 一种编程方法是创建一个连续循环,该循环使用 侦听 函数检查连接请求 (请参阅 在套接字) 上进行侦听 。 如果出现连接请求,应用程序将调用 accept、 AcceptEx或 WSAAccept 函数,并将工作传递到另一个线程来处理请求。
SOCKET ClientSocket;
ClientSocket = INVALID_SOCKET;
CleintSocket = accept(ListenSocket,NULL,NULL);
if(ClientSocket == INVALID_SOCKET){
printf("accept failed %d\n",WSAGetLastError());
WSACleanup();
return 1;
}
当客户端连接被接受后,服务器应用程序通常会将接受的客户端套接字传递 (上述示例代码中的 ClientSocket 变量) 到工作线程或 i/o 完成端口,并继续接受其他连接。
还有许多其他编程技术可用于侦听和接受多个连接。 其中包括使用 select 函数或 WSAPoll 函数。
收发数据
#define DEFAULT_BUFLEN 512
char recvbuf[DEFAULT_BUFLEN];
int iResult, iSendResult;
int recvbuflen = DEFAULT_BUFLEN;
// Receive until the peer shuts down the connection
do {
iResult = recv(ClientSocket, recvbuf, recvbuflen, 0);
if (iResult > 0) {
printf("Bytes received: %d\n", iResult);
// Echo the buffer back to the sender
iSendResult = send(ClientSocket, recvbuf, iResult, 0);
if (iSendResult == SOCKET_ERROR) {
printf("send failed: %d\n", WSAGetLastError());
closesocket(ClientSocket);
WSACleanup();
return 1;
}
printf("Bytes sent: %d\n", iSendResult);
} else if (iResult == 0)
printf("Connection closing...\n");
else {
printf("recv failed: %d\n", WSAGetLastError());
closesocket(ClientSocket);
WSACleanup();
return 1;
}
} while (iResult > 0);
send 和 recv函数都分别返回发送或接收的字节数的整数值或错误。 每个函数也采用相同的参数:活动套接字、 字符 缓冲区、要发送或接收的字节数以及使用的任何标志。
关闭套接字
当服务器将数据发送到客户端时,可以调用 shutdown 函数以指定 SD _ SEND 来关闭套接字的发送端。 这允许客户端释放此套接字的某些资源。 服务器应用程序仍然可以在套接字上接收数据。
// shutdown the send half of the connection since no more data will be sent
iResult = shutdown(ClientSocket, SD_SEND);
if (iResult == SOCKET_ERROR) {
printf("shutdown failed: %d\n", WSAGetLastError());
closesocket(ClientSocket);
WSACleanup();
return 1;
}
完整代码
#undef UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdlib.h>
#include <stdio.h>
// Need to link with Ws2_32.lib
#pragma comment (lib, "Ws2_32.lib")
// #pragma comment (lib, "Mswsock.lib")
#define DEFAULT_BUFLEN 512
#define DEFAULT_PORT "27015"
int __cdecl main(void)
{
WSADATA wsaData;
int iResult;
SOCKET ListenSocket = INVALID_SOCKET;
SOCKET ClientSocket = INVALID_SOCKET;
struct addrinfo *result = NULL;
struct addrinfo hints;
int iSendResult;
char recvbuf[DEFAULT_BUFLEN];
int recvbuflen = DEFAULT_BUFLEN;
// Initialize Winsock
iResult = WSAStartup(MAKEWORD(2,2), &wsaData);
if (iResult != 0) {
printf("WSAStartup failed with error: %d\n", iResult);
return 1;
}
ZeroMemory(&hints, sizeof(hints));
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
hints.ai_flags = AI_PASSIVE;
// Resolve the server address and port
iResult = getaddrinfo(NULL, DEFAULT_PORT, &hints, &result);
if ( iResult != 0 ) {
printf("getaddrinfo failed with error: %d\n", iResult);
WSACleanup();
return 1;
}
// Create a SOCKET for connecting to server
ListenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
if (ListenSocket == INVALID_SOCKET) {
printf("socket failed with error: %ld\n", WSAGetLastError());
freeaddrinfo(result);
WSACleanup();
return 1;
}
// Setup the TCP listening socket
iResult = bind( ListenSocket, result->ai_addr, (int)result->ai_addrlen);
if (iResult == SOCKET_ERROR) {
printf("bind failed with error: %d\n", WSAGetLastError());
freeaddrinfo(result);
closesocket(ListenSocket);
WSACleanup();
return 1;
}
freeaddrinfo(result);
iResult = listen(ListenSocket, SOMAXCONN);
if (iResult == SOCKET_ERROR) {
printf("listen failed with error: %d\n", WSAGetLastError());
closesocket(ListenSocket);
WSACleanup();
return 1;
}
// Accept a client socket
ClientSocket = accept(ListenSocket, NULL, NULL);
if (ClientSocket == INVALID_SOCKET) {
printf("accept failed with error: %d\n", WSAGetLastError());
closesocket(ListenSocket);
WSACleanup();
return 1;
}
// No longer need server socket
closesocket(ListenSocket);
// Receive until the peer shuts down the connection
do {
iResult = recv(ClientSocket, recvbuf, recvbuflen, 0);
if (iResult > 0) {
printf("Bytes received: %d\n", iResult);
// Echo the buffer back to the sender
iSendResult = send( ClientSocket, recvbuf, iResult, 0 );
if (iSendResult == SOCKET_ERROR) {
printf("send failed with error: %d\n", WSAGetLastError());
closesocket(ClientSocket);
WSACleanup();
return 1;
}
printf("Bytes sent: %d\n", iSendResult);
}
else if (iResult == 0)
printf("Connection closing...\n");
else {
printf("recv failed with error: %d\n", WSAGetLastError());
closesocket(ClientSocket);
WSACleanup();
return 1;
}
} while (iResult > 0);
// shutdown the connection since we're done
iResult = shutdown(ClientSocket, SD_SEND);
if (iResult == SOCKET_ERROR) {
printf("shutdown failed with error: %d\n", WSAGetLastError());
closesocket(ClientSocket);
WSACleanup();
return 1;
}
// cleanup
closesocket(ClientSocket);
WSACleanup();
return 0;
}
client
创建socket
声明一个包含 sockaddr结构的 addrinfo对象并初始化这些值
struct addrinfo *result = NULL,*ptr = NULL, hints;
ZeroMemory(&hints,sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
调用 getaddrinfo 函数,请求在命令行上传递的服务器名称的 IP 地址。 在此示例中,客户端将连接到的服务器上的 TCP 端口被默认 _ 端口定义为27015。 Getaddrinfo 函数以检查是否有错误的整数的形式返回其值
#define DEFAULT_PORT "27015"
// Resolve the server address and port
iResult = getaddrinfo(argv[1], DEFAULT_PORT, &hints, &result);
if (iResult != 0) {
printf("getaddrinfo failed: %d\n", iResult);
WSACleanup();
return 1;
}
创建ConnectSocket
// Attempt to connect to the first address returned by
// the call to getaddrinfo
ptr=result;
// Create a SOCKET for connecting to server
ConnectSocket = socket(ptr->ai_family, ptr->ai_socktype,
ptr->ai_protocol);
检查错误
if (ConnectSocket == INVALID_SOCKET) {
printf("Error at socket(): %ld\n", WSAGetLastError());
freeaddrinfo(result);
WSACleanup();
return 1;
}
连接socket
调用 connect 函数,将创建的套接字和 sockaddr 结构作为参数传递。 检查常规错误。
// Connect to server.
iResult = connect( ConnectSocket, ptr->ai_addr, (int)ptr->ai_addrlen);
if (iResult == SOCKET_ERROR) {
closesocket(ConnectSocket);
ConnectSocket = INVALID_SOCKET;
}
// Should really try the next address returned by getaddrinfo
// if the connect call failed
// But for this simple example we just free the resources
// returned by getaddrinfo and print an error message
freeaddrinfo(result);
if (ConnectSocket == INVALID_SOCKET) {
printf("Unable to connect to server!\n");
WSACleanup();
return 1;
}
getaddrinfo函数用于确定 sockaddr 结构中的值。 本示例使用 getaddrinfo 函数返回的第一个 IP 地址来指定 传递给连接的 sockaddr 结构。 如果 第 一个 IP 地址的连接调用失败,请尝试从 getaddrinfo 函数返回的链接列表中的下一个 addrinfo结构
收发数据
#define DEFAULT_BUFLEN 512
int recvbuflen = DEFAULT_BUFLEN;
const char *sendbuf = "this is a test";
char recvbuf[DEFAULT_BUFLEN];
int iResult;
// Send an initial buffer
iResult = send(ConnectSocket, sendbuf, (int) strlen(sendbuf), 0);
if (iResult == SOCKET_ERROR) {
printf("send failed: %d\n", WSAGetLastError());
closesocket(ConnectSocket);
WSACleanup();
return 1;
}
printf("Bytes Sent: %ld\n", iResult);
// shutdown the connection for sending since no more data will be sent
// the client can still use the ConnectSocket for receiving data
iResult = shutdown(ConnectSocket, SD_SEND);
if (iResult == SOCKET_ERROR) {
printf("shutdown failed: %d\n", WSAGetLastError());
closesocket(ConnectSocket);
WSACleanup();
return 1;
}
// Receive data until the server closes the connection
do {
iResult = recv(ConnectSocket, recvbuf, recvbuflen, 0);
if (iResult > 0)
printf("Bytes received: %d\n", iResult);
else if (iResult == 0)
printf("Connection closed\n");
else
printf("recv failed: %d\n", WSAGetLastError());
} while (iResult > 0);
send 和 recv函数都分别返回发送或接收的字节数的整数值或错误。 每个函数也采用相同的参数:活动套接字、 字符 缓冲区、要发送或接收的字节数以及使用的任何标志。
断开连接
客户端完成向服务器发送数据后,可以调用 shutdown 函数,指定 SD SEND 以关闭套接字 _ 的发送端。 这允许服务器释放此套接字的一些资源。 客户端应用程序仍可接收套接字上的数据
// shutdown the send half of the connection since no more data will be sent
iResult = shutdown(ConnectSocket, SD_SEND);
if (iResult == SOCKET_ERROR) {
printf("shutdown failed: %d\n", WSAGetLastError());
closesocket(ConnectSocket);
WSACleanup();
return 1;
}
客户端应用程序收到数据后,将调用 closesocket 函数来关闭套接字。
使用套接字 DLL 完成客户端Windows,将调用 WSACleanup函数以释放资源
// cleanup
closesocket(ConnectSocket);
WSACleanup();
return 0;
完整代码
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdlib.h>
#include <stdio.h>
// Need to link with Ws2_32.lib, Mswsock.lib, and Advapi32.lib
#pragma comment (lib, "Ws2_32.lib")
#pragma comment (lib, "Mswsock.lib")
#pragma comment (lib, "AdvApi32.lib")
#define DEFAULT_BUFLEN 512
#define DEFAULT_PORT "27015"
int __cdecl main(int argc, char **argv)
{
WSADATA wsaData;
SOCKET ConnectSocket = INVALID_SOCKET;
struct addrinfo *result = NULL,
*ptr = NULL,
hints;
const char *sendbuf = "this is a test";
char recvbuf[DEFAULT_BUFLEN];
int iResult;
int recvbuflen = DEFAULT_BUFLEN;
// Validate the parameters
if (argc != 2) {
printf("usage: %s server-name\n", argv[0]);
return 1;
}
// Initialize Winsock
iResult = WSAStartup(MAKEWORD(2,2), &wsaData);
if (iResult != 0) {
printf("WSAStartup failed with error: %d\n", iResult);
return 1;
}
ZeroMemory( &hints, sizeof(hints) );
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
// Resolve the server address and port
iResult = getaddrinfo(argv[1], DEFAULT_PORT, &hints, &result);
if ( iResult != 0 ) {
printf("getaddrinfo failed with error: %d\n", iResult);
WSACleanup();
return 1;
}
// Attempt to connect to an address until one succeeds
for(ptr=result; ptr != NULL ;ptr=ptr->ai_next) {
// Create a SOCKET for connecting to server
ConnectSocket = socket(ptr->ai_family, ptr->ai_socktype,
ptr->ai_protocol);
if (ConnectSocket == INVALID_SOCKET) {
printf("socket failed with error: %ld\n", WSAGetLastError());
WSACleanup();
return 1;
}
// Connect to server.
iResult = connect( ConnectSocket, ptr->ai_addr, (int)ptr->ai_addrlen);
if (iResult == SOCKET_ERROR) {
closesocket(ConnectSocket);
ConnectSocket = INVALID_SOCKET;
continue;
}
break;
}
freeaddrinfo(result);
if (ConnectSocket == INVALID_SOCKET) {
printf("Unable to connect to server!\n");
WSACleanup();
return 1;
}
// Send an initial buffer
iResult = send( ConnectSocket, sendbuf, (int)strlen(sendbuf), 0 );
if (iResult == SOCKET_ERROR) {
printf("send failed with error: %d\n", WSAGetLastError());
closesocket(ConnectSocket);
WSACleanup();
return 1;
}
printf("Bytes Sent: %ld\n", iResult);
// shutdown the connection since no more data will be sent
iResult = shutdown(ConnectSocket, SD_SEND);
if (iResult == SOCKET_ERROR) {
printf("shutdown failed with error: %d\n", WSAGetLastError());
closesocket(ConnectSocket);
WSACleanup();
return 1;
}
// Receive until the peer closes the connection
do {
iResult = recv(ConnectSocket, recvbuf, recvbuflen, 0);
if ( iResult > 0 )
printf("Bytes received: %d\n", iResult);
else if ( iResult == 0 )
printf("Connection closed\n");
else
printf("recv failed with error: %d\n", WSAGetLastError());
} while( iResult > 0 );
// cleanup
closesocket(ConnectSocket);
WSACleanup();
return 0;
}
udp
server
#include <winsock2.h>
#include<iostream>
#pragma comment(lib,"ws2_32.lib")
using namespace std;
int main()
{
WORD wVersion;
WSADATA wsaData;
int er;
//1.初始化版本信息
wVersion = MAKEWORD(1, 1);
//加载套接字库
er = WSAStartup(wVersion, &wsaData);
if (er != 0)
{
return -1;
}
//检测套接字版本信息
if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1)
{
WSACleanup();
return -2;
}
//2.创建服务器端套接字
SOCKET sockSer = socket(AF_INET, SOCK_DGRAM, 0);
SOCKADDR_IN addr_in;
addr_in.sin_family = AF_INET;//地址族
addr_in.sin_port = htons(7000);//端口
addr_in.sin_addr.S_un.S_addr = htonl(INADDR_ANY);//IP
bind(sockSer, (sockaddr*)&addr_in, sizeof(addr_in));
SOCKADDR_IN client_addr;
int len = sizeof(sockaddr_in);
char recvBuf[1024];
recvfrom(sockSer, recvBuf, sizeof(recvBuf), 0, (sockaddr*)&client_addr, &len);
cout << "接受到的数据:" << recvBuf << endl;
//向客户端发送数据
sendto(sockSer, recvBuf, sizeof(recvBuf), 0, (sockaddr*)&client_addr, len);
closesocket(sockSer);
WSACleanup();
system("pause");
return 0;
}
client
#include "stdafx.h"
#include <winsock2.h>
#include<iostream>
#pragma comment(lib,"ws2_32.lib")
using namespace std;
int main()
{
WORD wVersion;
WSADATA wsaData;
int er;
//1.初始化版本信息
wVersion = MAKEWORD(1, 1);
//加载套接字库
er = WSAStartup(wVersion, &wsaData);
if (er != 0)
{
return -1;
}
//检测套接字版本信息
if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1)
{
WSACleanup();
return -2;
}
//2.创建服务器端套接字
SOCKET sockSer = socket(AF_INET, SOCK_DGRAM, 0);
SOCKADDR_IN addr_in;
addr_in.sin_family = AF_INET;//地址族
addr_in.sin_port = htons(7000);//端口
addr_in.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
bind(sockSer, (sockaddr*)&addr_in, sizeof(addr_in));
SOCKADDR_IN addr;
int len = sizeof(addr);
//向服务器发送数据
char sendBuf[1024];
sendto(sockSer, sendBuf, sizeof(sendBuf), 0, (sockaddr*)&addr, len);
//接受数据
char recvBuf[1024];
recvfrom(sockSer, recvBuf, sizeof(recvBuf), 0, (sockaddr*)&addr, &len);
cout << "接受到服务器端的数据:" << recvBuf << endl;
closesocket();
WSACleanup();
system("pause");
return 0;
}