注意:此代码是在window平台下;linux需要自己更改部分代码,本文尽可能的使用c++标准所支持的规范来编写;
如果setsockopt返回-1且errno为22,则可能是setsockopt(s, SOL_SOCKET, SO_BROADCAST, char*, int), 你可以使用int,bool等等类型强转来达到目的,但是这样可能会出现返回-1且errno为22这个问题
一、简单介绍socket编程所需要使用的函数
1:socket函数
int sock(int af, int type, int protocol);
/*
* 作用:创建一个socket
*
* 参数:
* af: (address family)常见的一般有AF_INET、AF_INET6、AF_UNIX
* AF_INET : intetnetwork: UDP,TCP, etc
* AF_INET6: Internetwork Version 6
* AF_UNIX : local to host(pipes, portals)
*
* type: 常见的一般有SOCK_STREAM、SOCK_DGRAM
* SOCK_STREAM:stream socket
* SOCK_DGRAM :datadgram socket
*
* protocol:常见的一般有IPPROTO_TCP、IPPROTO_UDP等
* IPPROTO_TCP:tcp协议
* IPPROTO_UDP:udp协议
*
* return:
* ~0:创建失败,其他成功,(~是取反运算符,即0按位取反)
*/
2:bind函数
int bind(int s, const struct sockaddr * addr, int addrLen);
/*
* 作用:将套接字s绑定到指定地址addr
*
* s:传入的套接字,由socket函数得到
*
* addr:地址变量,包含family、port、ip_addr等信息,这里只是说有这些信息,不代表有这些成员
*
* addrLen:addr所占用的字节数
*
* return:
* -1表示失败,0表示成功,至于其他的我也不清楚,可以自行百度
*/
3:listen函数
int listen(int s, int backlog);
/*
* 作用:listen for connections on a socket(来自linux的man命令)
*
* s:套接字,由socket函数得到
*
* backlog:存储队列长度
*
* return:
* 0表示成功,-1表示失败,其他的不知道
*/
4:connect函数
int connect(int s, const struct sockaddr *addr, int addrLen);
/*
* 作用,尝试连接addr指定的服务器
*
* s:套接字,有socket得到
*
* addr:目标地址,一般包括family、port、ip_addr等信息,这里只是说有这些信息,不代表有这些成员
*
* addrLen:addr所占用的字节数
*
* return:
* 0成功,-1失败
*/
5:accept函数
int accept(int s, struct sockaddr *addr, int *addrLen);
/*
* 作用:accept a connection on a socket(来自linux的man命令)(man accept4)
*
* s:套接字,由socket函数得到
*
* addr:地址,发起连接的addr
*
* addrLen:是一个value-result参数,必须为addr占用的字节数,但函数执行完之后可能会改变
*
* return:
* -1表示失败,其他的:和发起连接的客户进行通信的套接字,也就是说和这个连接通信的话,可以使用这
* 个套接字
*/
6:send函数
int send(int s, const char *buff, int size, int flags);
/*
* 作用:发送数据
*
* s:套接字
*
* buff:数据地址
*
* size:发送数据的大小
*
* flags:the flahs argument is the bitwise OR of zero or more of the fllowing flags ...
* 有点多,自己查,我也不太懂,有会的可以教教我
*
* return:
* 实际发送的数据大小
*/
7:recv函数
int recv(int s, char *buff, int size, int flags);
/*
* 作用:接收数据
*
* s:套接字
*
* buff:数据地址
*
* size:最大接收
*
* flags:the flahs argument is formed by ORing one or more of the following values ...
* 有点多,自己查,我也不太懂,有会的可以教教我
*
* return:
* 实际接收到的数据大小
*/
二、demo
2.1:server.cpp
#include <iostream>
#include <string>
#include <thread>
#include <vector>
#include <map>
/*window下面socket编程需要的头文件和库*/
#include <WinSock2.h>
#pragma comment(lib, "ws2_32.lib")
//简单的封装一下标准库的thread
//就多了一个isFinished函数,用来判断当前thread是否执行完毕
class CThread
{
public:
explicit CThread() : m_thread(), m_isFinished(false) {}
~CThread() { if(m_thread.joinable()) m_thread.join(); }
CThread(const CThread*) = delete;
CThread &operator=(const CThread&) = delete;
CThread(CThread &&thread) : m_thread(std::move(thread.m_thread)), m_isFinished(thread.m_isFinished) {}
template<typename Fn, typename ... Args>
void run(Fn f, Args ... args)
{
m_thread = std::thread([this](Fn fn, Args ... args) {
fn(args ...);
m_isFinished = true;
}, f, args ...);
}
void join() { m_thread.join(); }
bool isFinished() { return m_isFinished; }
private:
std::thread m_thread;
bool m_isFinished;
};
//sock和对应的通信线程
std::map<int, CThread> mapSockThread;
//和sock代表的客户通信
void communication(int sock)
{
constexpr int maxSize = 2048;
char buff[maxSize];
while(true)
{
int recvBytes = recv(sock, buff, maxSize, 0);
if(recvBytes <= 0)
break;
std::cout << "recv : " << std::string(buff, recvBytes) << " from sock : " << sock << std::endl;
std::string message = "fuck! do not disturb me!!!";
int sendBytes = send(sock, message.c_str(), message.size(), 0);
}
}
//监听想连接serverSock的请求
void listenConnection(int serverSock)
{
sockaddr_in remoteAddr;
int size = sizeof(remoteAddr);
while(true)
{
std::vector<int> vecSock;
//找到所有已经通信结束了的
for(auto i = mapSockThread.begin(); i != mapSockThread.end(); ++i)
{
if(i->second.isFinished())
vecSock.push_back(i->first);
}
//清掉那些已经通信结束了的
for(auto i = vecSock.begin(); i != vecSock.end(); ++i)
{
closesocket(*i);
mapSockThread.erase(*i);
std::cout << "********erase : " << *i << "**********\n";
}
int remoteSock = accept(serverSock, (sockaddr*)(&remoteAddr), &size);
if(remoteSock != INVALID_SOCKET)
{
mapSockThread.insert({ remoteSock, CThread() });
mapSockThread.at(remoteSock).run(communication, remoteSock);
}
}
}
int main()
{
std::cout << "hello server ... " << std::endl;
//window下的socket通信,必须调用这个函数来初始化相应的模块
WSAData wsa;
WSAStartup(MAKEWORD(2, 2), &wsa);
int sock = socket(AF_INET, SOCK_STREAM, 0);
if(sock == INVALID_SOCKET)
{
std::cout << "socket error ...\n";
return 0;
}
sockaddr_in sockAddr;
std::memset(&sockAddr, 0, sizeof(sockAddr));
sockAddr.sin_family = AF_INET;
sockAddr.sin_port = htons(8888);
if(bind(sock, (const sockaddr*)(&sockAddr), sizeof(sockAddr)) == -1)
{
std::cout << "bind error ...\n";
return 0;
}
if(listen(sock, 5) == -1)
{
std::cout << "listen error ...\n";
return 0;
}
std::thread listenThread(listenConnection, sock);
listenThread.join();
//释放服务器资源
closesocket(sock);
//释放资源
WSACleanup();
return 0;
}
2.2:client.cpp
#include <iostream>
#include <string>
#include <WinSock2.h>
#pragma comment(lib, "ws2_32.lib");
int main()
{
std::cout << "hello client ...\n";
WSAData wsa;
WSAStartup(MAKEWORD(2, 2), &wsa);
int sock = socket(AF_INET, SOCK_STREAM, 0);
if(sock == INVALID_SOCKET)
{
std::cout << "socket error ...\n";
return 0;
}
//要连接的服务器地址
sockaddr_in serverAddr;
std::memset(&serverAddr, 0, sizeof(serverAddr));
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(8888);
//这里你可以用其他方法设置ip地址
//这里是127.0.0.1代表本机,想连接其他电脑,换成对应电脑ip即可
serverAddr.sin_addr.S_un.S_un_b.s_b1 = 127;
serverAddr.sin_addr.S_un.S_un_b.s_b2 = 0;
serverAddr.sin_addr.S_un.S_un_b.s_b3 = 0;
serverAddr.sin_addr.S_un.S_un_b.s_b4 = 1;
//请求将本地sock和服务器serverAddr连接起来
if(connect(sock, (sockaddr*)(&serverAddr), sizeof(serverAddr)) == -1)
{
std::cout << "connect error ...\n";
return 0;
}
constexpr int maxSize = 2048;
char buff[maxSize];
std::string message;
while(true)
{
std::cout << "send : ";
std::cin >> message;
if(message == "quit")
break;
int sendBytes = send(sock, message.c_str(), message.size(), 0);
if(sendBytes > 0)
{
int recvBytes = recv(sock, buff, maxSize, 0);
if(recvBytes > 0)
{
std::cout << "recv : " << std::string(buff, recvBytes) << std::endl;
}
}
}
closesocket(sock);
}
三、demo运行
1:运行server
2:运行client(可以运行多个client,即多个client连接同一个服务器)
3:在client中随便输入一些数据,然后回车,服务器会回“fuck! do not disturb me!!!”,当然你可以找到对应的代码进行修改