作者:小 琛
欢迎转载,请标明出处
Windows下的socket与Linux下有些许不同,有关Windows下socket初始化内容,翻看前篇博客:
Windows下socket初始化
客户端与服务端编程流程
相关函数
创建套接字
网络编程中,绕不开的一个话题:字节序问题
- addrinfo 结构体
typedef struct addrinfo {
int ai_flags;
int ai_family;
int ai_socktype;
int ai_protocol;
size_t ai_addrlen;
char *ai_canonname;
struct sockaddr *ai_addr;
struct addrinfo *ai_next;
} ADDRINFOA, *PADDRINFOA;
- Getaddrinfo()函数
INT WSAAPI getaddrinfo(
[in, optional] PCSTR pNodeName,
[in, optional] PCSTR pServiceName,
[in, optional] const ADDRINFOA *pHints,
[out] PADDRINFOA *ppResult );
参数1:ip地址,为空则默认主机地址
参数2:端口号
参数3:输入的参数,相关设定
参数4:输出参数,后续使用
返回值:0为成功,非0失败
使用方法:用addrinfo 结构体填充信息后,用getaddrinfo函数得到后续使用的网络编程addrinfo结构体。
#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdlib.h>
#include <iostream>
#define port "1999"
#pragma comment (lib, "Ws2_32.lib")
#pragma comment (lib, "Mswsock.lib")
#pragma comment (lib, "AdvApi32.lib")
using std::cout;
using std::endl;
int main() {
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
printf("WSAStartup error\n");
return 0;
}
//打印相关信息
cout << "Version is :" << LOBYTE(wsaData.wVersion) << "." << HIBYTE(wsaData.wVersion) << std::endl;
cout << "High Version is :" << LOBYTE(wsaData.wHighVersion) << "." << HIBYTE(wsaData.wHighVersion) << std::endl;
cout << "Description: " << wsaData.szDescription << endl;
cout << "SystemStatus: " << wsaData.szSystemStatus << endl;
cout << "Max Count Socket:" << wsaData.iMaxSockets << endl;
cout << "Max UdnDn: " << wsaData.iMaxUdpDg << endl;
struct addrinfo* result;
struct addrinfo addr;
//设定
ZeroMemory(&addr, sizeof(addr));
addr.ai_family = AF_UNSPEC;
addr.ai_socktype = SOCK_STREAM;
addr.ai_protocol = IPPROTO_TCP;
int res = getaddrinfo(NULL, "1999", &addr, &result);
cout << result->ai_family << "->" << result->ai_addr << "->" << result->ai_next << std::endl;
}
绑定套接字
int bind (int sockfd, const struct sockaddr* addr, socklen_t addrlen)
参数:
- sockfd:套接字操作句柄
- addr:为更好的兼容不同协议的地址信息而设定的结构体
- struct sockaddr_in
{
sin_family;//告诉内核以何种协议去解析,例如传AF_INET即ipv4
sin_port;端口信息
sin_addr.s_addr;uint32_t类型的ip地址
}
bool Bind(string& ip, unsigned short port){
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.S_un.S_addr = inet_addr(ip.c_str());
int len = sizeof(struct sockaddr_in);
int res = bind(sock, (struct sockaddr*)&addr, len);
if (res = SOCKET_ERROR){
cout << "bind error" << WSAGetLastError()<< endl;
return false;
}
return true;
}
侦听套接字
监听:
int listen(int sockfd, int backlog )
参数:
- sockfd:套接字描述符
- backlog:已完成连接队列的大小。可以指定大小,若当前队列已满,会丢弃新来的连接,从以完成连接队列中获取创建完成的新连接
返回值:
失败返回-1,否则返回1
3.监听状态
已完成连接队列:三次握手完成的连接集合
未完成连接队列:处于三次握手当中的连接集合
bool Listen(int count = 5){
int res = listen(sock, count);
if (res == SOCKET_ERROR){
cout << "listen error" << endl;
return false;
}
return true;
}
连接套接字
发送连接 int connect(int sockfd, const struct sockaddr* addr, socklen_t
addrlen)
参数
- sockfd:套接字描述符
- addr:服务端地址信息,需要在代码中配置
- addrlen:地址信息长度
- 返回值
失败返回-1,否则为1
bool Connect(string& ip, unsigned short port){
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.S_un.S_addr = inet_addr(ip.c_str());
int len = sizeof(sockaddr_in);
int res = connect(sock, (struct sockaddr*)&addr, len);
if (res == SOCKET_ERROR){
cout << "connect error" << endl;
return false;
}
return true;
}
接受连接
获取连接 int accept (int sockfd, struct sockaddr* addr,socklen_t addrlen)
参数
- sockfd:侦听套接字描述符
- addr:客户端的地址信息
- addrlen:客户端地址信息长度
- 返回值
返回新创建出来socket的sockfd,通过新创建出来的socket为客户端服务。
bool Accept(struct sockaddr_in* addr, WinSockTcp* ts){
int len = sizeof(addr);
ts->sock = accept(sock, (struct sockaddr*)addr, &len);
if (ts->sock == INVALID_SOCKET){
cout << "accept error:" << WSAGetLastError() << endl;
return false;
}
return true;
}
注意
该接口是从已完成连接的队列中获取连接,因此当已完成队列中没有新的连接时候,会阻塞,知道获取新的连接
发送、接收数据
发送数据 sszie_t send(int sockfd, const void* buf, size_t len, int flags)
参数
- sockfd:套接字描述符,要使用accept的返回值
- buf:要发送的数据
- len:发送的数据长度
- flags:发送方式,0为阻塞发送
- 返回值:
大于0:发送数据的长度
等于0:标识对端关闭了连接
小于0:失败
接收数据 ssize_t recv(int sockfd, void* buf, size_t len, int flags)
参数
- sockfd:套接字描述符,要使用accept的返回值
- buf:接收数据的存放
- len:最大接收长度
- flags:接收方式,0为阻塞接收,MSG_PEEK为探测接收
- 返回值
大于0:发送数据的长度
等于0:标识对端关闭了连接
小于0:失败
bool Send(const string data){
int size = send(sock, data.c_str(), data.size(), 0);
if (size < 0){
cout << "send error" << endl;
return false;
}
return true;
}
bool Recv(string* data){
char buff[1024];
int size = recv(sock, buff, sizeof(buff), 0);
if (size < 0){
cout << "recv error" << endl;
return false;
}
else if (size == 0){
cout << "peer shutdown connect" << endl;
return false;
}
else{
(*data).assign(buff, size);
return true;
}
}
断开连接
- 当服务器将数据发送到客户端时,可以调用 shutdown 函数以指定 SD _ SEND 来关闭套接字的发送端。 这允许客户端释放此套接字的某些资源。 服务器应用程序仍然可以在套接字上接收数据。
iResult = shutdown(ClientSocket, SD_SEND);
if (iResult == SOCKET_ERROR) {
printf("shutdown failed: %d\n", WSAGetLastError());
closesocket(ClientSocket);
WSACleanup();
return 1;
}
- 当客户端应用程序接收到数据后,将调用 closesocket函数来关闭套接字。
当使用 Windows 套接字 DLL 完成客户端应用程序时,将调用 WSACleanup函数以释放资源。
// cleanup
closesocket(ClientSocket);
WSACleanup();
return 0;
实战
将socket所有的操作写成一个类,winsock.h
#include <iostream>
#include <vector>
#include <winsock2.h>
#include <string>
#pragma comment(lib,"ws2_32.lib")
using std::cin;
using std::cout;
using std::endl;
using std::vector;
using std::string;
class WinSockTcp
{
private:
SOCKET sock;
public:
WinSockTcp(){}
~WinSockTcp(){}
/*
函数功能:WinSock的初始化
输入参数:WSAData结构体,作为一个输出参数,包含版本信息
返回值:成功返回true,失败返回false
*/
bool StartUp(WSAData& wsadata){
int res = WSAStartup(MAKEWORD(2, 2), &wsadata);
if (res != 0){
return false;
cout << "SOCKET StartUp Error" << endl;
}
else{
return true;
}
}
/*
函数功能:WinSock的创建
输入参数:无
返回值:成功返回true,失败返回false
*/
bool CreateSocket(){
sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sock == INVALID_SOCKET){
cout << "Create Socket Error" << endl;
return false;
}
return true;
}
/*
函数功能:绑定地址信息
输入参数:需绑定的ip,需绑定的端口
返回值:成功返回true,失败返回false
*/
bool Bind(string& ip, unsigned short port){
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.S_un.S_addr = inet_addr(ip.c_str());
int len = sizeof(struct sockaddr_in);
int res = bind(sock, (struct sockaddr*)&addr, len);
if (res == SOCKET_ERROR){
cout << "bind error" << WSAGetLastError()<< endl;
return false;
}
return true;
}
/*
函数功能:开始监听
输入参数:监听队列的最大长度
返回值:成功返回true,失败返回false
*/
bool Listen(int count = 5){
int res = listen(sock, count);
if (res == SOCKET_ERROR){
cout << "listen error" << endl;
return false;
}
return true;
}
/*
函数功能:接收连接函数
输入参数:sockaddr_in结构体用于得到连接者信息,新创建的服务socket的类
返回值:成功返回true,失败返回false
*/
bool Accept(struct sockaddr_in* addr, WinSockTcp* ts){
int len = sizeof(addr);
ts->sock = accept(sock, (struct sockaddr*)addr, &len);
if (ts->sock == INVALID_SOCKET){
cout << "accept error:" << WSAGetLastError() << endl;
return false;
}
return true;
}
/*
函数功能:发起连接函数
输入参数:发送连接请求的ip,发送连接请求的端口
返回值:成功返回true,失败返回false
*/
bool Connect(string& ip, unsigned short port){
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.S_un.S_addr = inet_addr(ip.c_str());
int len = sizeof(sockaddr_in);
int res = connect(sock, (struct sockaddr*)&addr, len);
if (res == SOCKET_ERROR){
cout << "connect error" << endl;
return false;
}
return true;
}
/*
函数功能:发送数据端口
输入参数:需要发送的数据
返回值:成功返回true,失败返回false
*/
bool Send(const string data){
int size = send(sock, data.c_str(), data.size(), 0);
if (size < 0){
cout << "send error" << endl;
return false;
}
return true;
}/*
函数功能:接收数据端口
输入参数:接收数据的string
返回值:成功接收返回true,失败返回false
*/
bool Recv(string* data){
char buff[1024];
int size = recv(sock, buff, sizeof(buff), 0);
if (size < 0){
cout << "recv error" << endl;
return false;
}
else if (size == 0){
cout << "peer shutdown connect" << endl;
return false;
}
else{
(*data).assign(buff, size);
return true;
}
}
/*
函数功能:关闭socket,释放资源
输入参数:无
返回值:无
*/
void CloseSocket(){
closesocket(sock);
sock = -1;
}
/*
函数功能:禁止socket
输入参数:无
返回值:无
*/
void ShutDownSocket(){
shutdown(sock,SD_BOTH);
}
static void CleanUp(){
WSACleanup();
}
};
client.cc
int main(int argc, char*argv[])
{
//if (argc != 3)
//{
// printf("Start Server: ./svr [ip] [port]\n");
// return 0;
//}
//string ip = argv[1];
//unsigned int port = atoi(argv[2]);
string ip = "192.168.152.150";
u_short port = 1999;
WinSockTcp ts;
WSADATA wsadata;
if (!ts.StartUp(wsadata)){
return 0;
}
if (!ts.Connect(ip, port))
return 0;
while (1){
fflush(stdout);
string data;
cin >> data;
ts.Send(data);
ts.Recv(&data);
cout << "server say:" << data << endl;
}
ts.ShutDownSocket();
ts.CreateSocket();
return 0;
}
server.h
int main(int argc, char*argv[])
{
/*if (argc != 3)
{
printf("Start Server: ./svr [ip] [port]\n");
return 0;
}
string ip = argv[1];
unsigned int port = atoi(argv[2]);*/
u_short port = 1999;
string ip = "0.0.0.0";
WinSockTcp ts;
WSADATA wsadata;
if (!ts.StartUp(wsadata))
return 0;
if (!ts.CreateSocket())
return 0;
if (!ts.Bind(ip, port))
return 0;
if (!ts.Listen(10))
return 0;
WinSockTcp svrsock;
while (1){
struct sockaddr_in addr;
if (!ts.Accept(&addr, &svrsock))
return 0;
cout << "have new connect ,ip:" << addr.sin_addr.S_un.S_addr << "port:" << addr.sin_port << endl;
string data;
svrsock.Recv(&data);
cout << "client say:" << data.c_str() << endl;
cout << "please enter :" << endl;
cin >> data;
svrsock.Send(data);
}
svrsock.ShutDownSocket();
svrsock.CloseSocket();
ts.ShutDownSocket();
ts.CloseSocket();
ts.CleanUp();
return 0;
}