笔记目录导航
......
网络通信可以直接调用socket系统api接口进行通信,不过在不同的系统中,部分代码是有区别的,Windows和Linux系统中 一些接口或者接口参数的类型是有区别的,需要进行兼容,所以通常会将底层的socket api接口进行封装,已达到跨平台的目的,因为这里是C++语言,所以封装成一个类。
差别之处利用宏判断是windows系统或者是其他系统,这里只处理Windows系统和Linux系统
#ifdef _WIN32
这里是Windows系统下的接口调用
#else
这里是Linux系统下的接口调用
#endif
Socket类头文件如下:
#ifndef _SOCKET_H_
#define _SOCKET_H_
/*
基础socket类,封装c socket接口
*/
#define _CRT_SECURE_NO_WARNINGS
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <assert.h>
#ifdef _WIN32
#include <WinSock2.h>
#pragma comment(lib, "ws2_32.lib")
//由于bzero在windows系统下没有 这里定义一个bzero
#define bzero(point, size) memset(point, 0, size)
#else
#include <string.h>
//统一socket句柄的类型名
typedef int SOCKET;
#define INVALID_HANDLE_VALUE (-1)
#endif
class Socket
{
public:
Socket();
Socket(SOCKET sock, const char* ip, int port);
~Socket();
//Windows系统中使用socket函数需要先调用WSAStartup需要初始化版本
static bool InitSocket();
//Windows系统中使用socket函数完毕后需要调用WSACleanup释放
static void UnInitSocket();
//设置可重用地址
void SetReuseAddr();
//设置非阻塞
void SetSockNonBlock();
//设置接收缓冲区大小
void SetRecvBuffSize(int size);
//设置发送缓冲区大小
void SetSendBuffSize(int size);
//地址绑定
bool Bind(const char* ip, int port);
//启动监听
bool Listen(int backlog);
//接收连接,返回一个已分配内存上的Socket指针, 需要手动管理释放,失败返回 nullptr
Socket* Accept();
//接收连接, 返回连接的socket
SOCKET Accept(struct sockaddr_in * addr, int * addrlen);
//发起连接
int Connect(const char* ip, int port);
//发起连接
int Connect(SOCKET sock, const char* ip, int port);
//发送数据
int Send(const char* buf, int size);
//接收数据
int Recv(char* buf, int size);
//关闭套接字
int Close();
public:
SOCKET m_Sock;
char m_Ip[16];
short m_Port;
};
#endif
InitSocket() 和UnInitSocket() 内部有做分平台处理,因为在windows下 socket通信需要一个初始化和释放的步骤,需要进行兼容
SetReuseAddr():设置可重用地址接口, 设置端口可以被重复使用,可以被多个进程bind, 防止服务器端先调用close关闭socket后系统处于TIME_WAIT状态下还没有释放端口,这时重新启服的时候 在调用bind时 会绑定失败,提示ADDR已经在使用中。
SetSockNonBlock():设置非阻塞,socket默认是阻塞模式,例如调用recv的时候 程序会阻塞在这里,一直等到读取到数据后,才会返回读取到的字节数,设置了非阻塞后,调用recv函数的时候会立即返回读取到的字节数,如果没有字节可读取,则会返回对应的错误码, 后续会涉及到使用多路IO复用机制中的Epoll,需要设置socket为非阻塞模式
SetRecvBuffSize():设置socket的接收缓冲区大小
SetSendBuffSize():设置socket的发送缓冲区大小
系统中会为每个创建的socket分配一个接收缓冲区和一个发送缓冲区,例如调用send()函数发送一段数据,函数返回时,数据实际上是被加到了socket的发送缓冲区中,并没有实时通过底层网卡发送出去,会由系统在合适的时候再来将发送缓冲区中的数据发送出去。可以根据程序对网络通信数据量和网络的情况设置合理大小来提高效率和避免数据错误等问题。
Socket类的Cpp文件如下:
#include "Socket.h"
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#if _WIN32
#else
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#endif
Socket::Socket()
{
m_Sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
assert(m_Sock != -1);
SetReuseAddr();
}
Socket::Socket(SOCKET sock, const char* ip, int port) {
m_Sock = sock;
strncpy(m_Ip, ip, sizeof(ip));
m_Port = port;
SetReuseAddr();
}
Socket::~Socket(){
Close();
}
bool Socket::InitSocket() {
#if _WIN32
WORD wVersionRequested = MAKEWORD(2, 2);
WSADATA wsaData;
int nErrorID = ::WSAStartup(wVersionRequested, &wsaData);
if (nErrorID != 0) return false;
if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) {
UnInitSocket();
return false;
}
#endif
return true;
}
void Socket::UnInitSocket() {
#if _WIN32
::WSACleanup();
#endif
}
void Socket::SetReuseAddr() {
int on = 1;
setsockopt(m_Sock, SOL_SOCKET, SO_REUSEADDR, (const char*)&on, sizeof(on));
}
void Socket::SetSockNonBlock()
{
int ret = -1;
#if _WIN32
u_long argp = 1;
ret = ioctlsocket(m_Sock, FIONBIO, &argp);
#else
int old_flag = fcntl(m_Sock, F_GETFL, 0);
ret = fcntl(m_Sock, F_SETFL, old_flag | O_NONBLOCK);
#endif
if (ret == -1) {
return;
}
}
void Socket::SetRecvBuffSize(int size)
{
int ret = -1;
#if _WIN32
int len = sizeof(size);
ret = setsockopt(m_Sock, SOL_SOCKET, SO_RCVBUF, (char *)&size, len);
#else
socklen_t len = sizeof(size);
ret = setsockopt(m_Sock, SOL_SOCKET, SO_RCVBUF, &size, len);
#endif
if (ret < 0){
return;
}
}
void Socket::SetSendBuffSize(int size)
{
int ret = -1;
#if _WIN32
int len = sizeof(size);
ret = setsockopt(m_Sock, SOL_SOCKET, SO_SNDBUF, (char *)&size, len);
#else
socklen_t len = sizeof(size);
ret = setsockopt(m_Sock, SOL_SOCKET, SO_SNDBUF, &size, len);
#endif
if (ret < 0) {
return;
}
}
bool Socket::Bind(const char * ip, int port)
{
struct sockaddr_in addr;
bzero(&addr, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
if (ip == nullptr)
addr.sin_addr.s_addr = htonl(INADDR_ANY);
else
#if _WIN32
addr.sin_addr.s_addr = inet_addr(ip);
#else
inet_pton(AF_INET, ip, &addr.sin_addr.s_addr);
#endif
int ret = bind(m_Sock, (struct sockaddr *)&addr, sizeof(addr));
if (ret < 0) {
return false;
}
return true;
}
bool Socket::Listen( int backlog)
{
int ret = listen(m_Sock, backlog);
if (ret == -1) {
return false;
}
return true;
}
Socket* Socket::Accept()
{
struct sockaddr_in addr;
#if _WIN32
int len = sizeof(addr);
#else
socklen_t len = sizeof(addr);
#endif
SOCKET sock = accept(m_Sock, (struct sockaddr*)&addr, &len);
if (-1 == sock)
return nullptr;
Socket* pSock = new Socket(sock, inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));
return pSock;
}
SOCKET Socket::Accept(struct sockaddr_in* addr, int * addrlen)
{
#if _WIN32
SOCKET sock = accept(m_Sock, (struct sockaddr*)addr, addrlen);
#else
SOCKET sock = accept(m_Sock, (struct sockaddr*)addr,(socklen_t *)addrlen);
#endif
return sock;
}
int Socket::Connect(const char* ip, int port)
{
return Connect(m_Sock, ip, port);
}
int Socket::Connect(SOCKET Sock, const char* ip, int port)
{
struct sockaddr_in addr;
bzero(&addr, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
#if _WIN32
int len = sizeof(addr);
addr.sin_addr.s_addr = inet_addr(ip);
#else
socklen_t len = sizeof(addr);
inet_pton(AF_INET, ip, &addr.sin_addr.s_addr);
#endif
return connect(Sock, (struct sockaddr*)&addr, len);;
}
int Socket::Send(const char* buf, int Len)
{
return send(m_Sock, buf, Len, 0);;
}
int Socket::Recv(char* buf, int Len)
{
return recv(m_Sock, buf, Len, 0);
}
int Socket::Close()
{
#if _WIN32
int ret = closesocket(m_Sock);
#else
int ret = close(m_Sock);
#endif
return ret;
}
这部分代码都比较简单,只是在底层api接口上分平台处理了差异之处,测试代码如下:
#include <iostream>
#include "net\Socket.h"
using namespace std;
int main(void)
{
Socket::InitSocket();
cout << "init Socket" << endl;
Socket * pListenSock = new Socket();
pListenSock->Bind(nullptr, 8888);
pListenSock->Listen(100);
Socket * pSock = pListenSock->Accept();
assert(pSock != nullptr);
char buf[10240];
bzero(buf, 10240);
while (true) {
int ret = pSock->Recv(buf, 10240);
if (ret <= 0) break;
cout << "recv data:" << buf << endl;
if (buf[0] == '#') break;
bzero(buf, 10240);
}
delete pSock;
delete pListenSock;
Socket::UnInitSocket();
cout << "My ServerEngine" << endl;
system("pause");
return 0;
}