简单socket聊天小程序+socket简单封装

本博客内容:
一、单线程简单socket聊天小程序
二、windows socket套接字简单封装
三、IP地址的表示形式与各个转换函数

一、单线程简单socket聊天小程序

客户端

#include<WinSock2.h>
#include<stdio.h>

//定义程序中使用的常量
#define SERVER_ADDRESS "127.0.0.1" //服务器端IP地址
#define PORT    5150    //服务器端的端口号
#define MSGSIZE 1024  //收发缓冲区的大小

//库文件
#pragma comment(lib,"ws2_32.lib")

int main()
{
//1.初始化
    //该结构体包含系统所支持的winsock版本信息
    WSADATA wsaData; 
    //初始化Winsock2.2
    if(WSAStartup(0x0202,&wsaData)!=0)  //WSAStartup(MAKWWORD(2,2),&wsaData)
    {
        printf("无法初始化!"); return 0;
    }
//2.socket套接字
    //定义客户端连接所用的套接字
    SOCKET sClient;  
    //参数1:TCP/IP协议族,参数2:TCP协议,UDP使用SOCK_DGRAM 
    sClient=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);

    if(sClient==INVALID_SOCKET)
    {
            printf("socket failed");
            return 0;
    }
//3.远端服务器
    //保存远端服务器地址
    SOCKADDR_IN server; 
    //置0操作
    memset(&server,0,sizeof(SOCKADDR_IN));
    //指定远端服务器的3个参数
    server.sin_family=AF_INET; //指定地址家族
    server.sin_port=htons(PORT);//指定端口号
    server.sin_addr.s_addr=inet_addr(SERVER_ADDRESS);//server.sin_addr.s_addr=htonl(SERVER_ADDRESS)
            //sin_addr字段用于保存IP地址,sin_addr字段也是一个结构体,sin_addr.s_addr用于保存最终的IP地址
            //inet_addr()函数用于将形如"127.0.0.1"字符串转换为IP地址格式

//4.连接到服务器
    connect(sClient,(struct sockaddr *)&server,sizeof(SOCKADDR_IN)); 

//5.建立连接后传输数据
    char  szMessage[MSGSIZE]; //收发缓冲区
    int ret; //成功接收到的字节数
    while(TRUE)
    {
        printf("Send:");
        //从键盘输入
        gets(szMessage);
        //发送数据
        //指明发送数据的套接字,待发送数据的保存地址,指明数据长度
        send(sClient,szMessage,strlen(szMessage),0);

        //接收数据
        ret=recv(sClient,szMessage,MSGSIZE,0);
        szMessage[ret]='\0'; //添加这个截断字符

        printf("Received [%d bytes ]: '%s'\n",ret,szMessage);

    }
//6.释放资源和结束工作
    closesocket(sClient);
    WSACleanup();
    return 0;
}

服务端

#include<WinSock2.h>
#include<stdio.h>

#define PORT 5150
#define MSGSIZE 1024

#pragma comment(lib,"ws2_32.lib")

int main()
{
//1.初始化
    WSADATA     wsaData;
    WSAStartup(0x0202,&wsaData);

//2.创建服务端+客户端套接字  地址
    SOCKET sListen;
    sListen=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
    SOCKET sClient; //无需初始化

//3. 服务端自身地址的设置
    SOCKADDR_IN local;
    SOCKADDR_IN client;  //客户端的地址 此处无需处理
    local.sin_family=AF_INET;
    local.sin_port=htons(PORT);
    local.sin_addr.s_addr=htonl(INADDR_ANY);

//4.定义一个客户端地址+绑定操作

    bind(sListen,(struct sockaddr * ) &local ,sizeof(SOCKADDR_IN));

//5.将socket设置为监听模式
    listen(sListen,1);   //参数2表示可以监听的个数

//6.接收客户端发送的连接请求
    int iaddrSize=sizeof(SOCKADDR_IN);
    sClient=accept(sListen,(struct sockaddr *)&client,&iaddrSize);  //注意此处最后一个参数是地址,而不是长度和bind函数不同

    //打印客户端的部分信息
     printf("Accepted client:%s:%d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));

//7.传输数据
     char szMessage[MSGSIZE];  //收发缓冲区
     int ret ; //收到的个数
     while(TRUE)
     {
         ret=recv(sClient,szMessage,MSGSIZE,0);
         szMessage[ret]='\0';
          printf("Received [%d bytes]: '%s'\n", ret, szMessage);
        printf("Send:");
        //从键盘输入
        gets(szMessage);   
        send(sClient,szMessage,strlen(szMessage),0); 
     }
     return 0;
}

二、windows socket套接字简单封装

参考:https://blog.csdn.net/gaoanchen/article/details/49019821

客户端头文件

#pragma once
#include<WinSock2.h>
#include<stdio.h>
#pragma comment(lib,"ws2_32.lib")
#include<assert.h>  //因为需要对很多API函数的返回值检测,使用assert断言来处理错误

//宏定义
#define TCP_DATA 1
#define UDP_DATA 2

//TCP连接限制
#define MAX_TCP_CONNECT 10

//缓冲区上限
#define MAX_BUFFER_LEN 1024

//客户端类
class Client
{
public:
    Client(); 
    virtual ~Client();
    void init(int inet_type,char*addr,unsigned short port);  //通信协议,地址,端口号
    void sendData(const char * buff,const int len);
    void getData(char* buff,const int len);

    //get函数
    char* getProto();
    char* getIP();
    unsigned short getPort();
private:
    int m_type;//通信协议类型
    SOCKET m_socket;  //本地套接字
    sockaddr_in serveraddr; //服务器地址结构
};

客户端源文件

#include"A.h"
Client::Client()
{
    WSADATA wsa;
    int rslt=WSAStartup(0x0202,&wsa);
    assert(rslt==0);
};
Client::~Client()
{
    if(m_socket!=INVALID_SOCKET)
        closesocket(m_socket);
   WSACleanup(); //卸载WinSock DLL
}
void Client::init(int inet_type,char* addr,unsigned short port)
{
    int rslt;
    m_type=inet_type;
    if(m_type==TCP_DATA)
        m_socket=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
    else if(m_type==UDP_DATA)
        m_socket=socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
    assert(m_socket!=INVALID_SOCKET);
    //设置服务器的地址结构体
    serveraddr.sin_family=AF_INET;
    serveraddr.sin_addr.S_un.S_addr=inet_addr(addr);
    serveraddr.sin_port=htons(port);
    memset(serveraddr.sin_zero,0,8);
    if(m_type==TCP_DATA)
    {
        rslt=connect(m_socket,(sockaddr*)&serveraddr,sizeof(sockaddr)); //客户端请求
        assert(rslt==0);
    }
}
void Client::sendData(const char*buff,const int len)
{
    int rslt;
    if(m_type==TCP_DATA)
    rslt=   send(m_socket,buff,len,0);
    else if(m_type==UDP_DATA)
        rslt=sendto(m_socket,buff,len,0,(sockaddr*)&serveraddr,sizeof(serveraddr));
    if(SOCKET_ERROR==rslt)
    {
        printf("send failed.\n");
        closesocket(m_socket);
        WSACleanup();
    }
}
void Client::getData(char* buff,const int len)
{
    int rslt;
    int addrLen=sizeof(sockaddr_in);
    memset(buff,0,len);
    if(m_type==TCP_DATA)
        rslt=recv(m_socket,buff,len,0);
    else if(m_type==UDP_DATA)
        rslt=recvfrom(m_socket,buff,len,0,(sockaddr *)&serveraddr,&addrLen);
    assert(rslt>0);
}
char* Client::getProto()
{
    if(m_type==TCP_DATA)
        return "TCP";
    else if(m_type==UDP_DATA)
        return "UDP";
    else 
        return "";
}
char * Client::getIP()
{
    return inet_ntoa(serveraddr.sin_addr);
}
unsigned short Client::getPort()
{
    return ntohs(serveraddr.sin_port);
}

服务端头文件

#pragma once
//服务端因为要处理多个客户端连接请求,因此需要为每个客户端开辟新的进程
#include<WinSock2.h>
#include<Windows.h>
#include<list>
#include<stdio.h>
#include<assert.h>
#include<iostream>
using namespace std;

//宏定义
#define TCP_DATA 1
#define UDP_DATA 2

//TCP连接限制
#define MAX_TCP_CONNECT 10

//缓冲区上限
#define MAX_BUFFER_LEN 1024

//服务端类
class Server
{
public:
    Server(void);
    ~Server(void);
    void init(int inet_type,char*addr,unsigned short port);
    void start(); //启动服务器
    virtual void connect(sockaddr_in * client); //连接时候处理
    virtual int procRequest(sockaddr_in * client,const char* req,int reqLen,char*resp); //处理客户端请求
    virtual void disConnect(sockaddr_in * client);//断开时候处理
    //get函数
    char* getProto();
    char* getIP(sockaddr_in * serverAddr=NULL); //获取IP
    unsigned short getPort(sockaddr_in *serverAddr=NULL); //获取端口
private:
    CRITICAL_SECTION * cs; //临界区对象
    int m_type; 
    SOCKET m_socket;
    sockaddr_in serverAddr; //服务端地址
    list<sockaddr_in*> clientAddrs; //客户端地址结构列表
    sockaddr_in* addClient(sockaddr_in client); //添加客户端地址结构
    void delClient(sockaddr_in * client);  //删除客户端的地址结构
    friend DWORD WINAPI threadProc(LPVOID lpParam); //线程处理函数作为友元函数

};

服务端源文件

#include "Server.h"
//服务器端线程处理函数结构
struct SockParam
{
    SOCKET rsock; //远程的socket
    sockaddr_in *raddr;  //远程地址结构
    Server * pServer;  //服务器对象指针
     SockParam(SOCKET rs,sockaddr_in * ra,Server *ps)
     {
         rsock=rs;
         raddr=ra;
         pServer=ps;
     }
};
DWORD WINAPI threadProc(LPVOID lpParam)
{
    SockParam sp=*(SockParam*)lpParam;   //对传入的参数进行解析
    Server *s=sp.pServer;
    SOCKET sock=s->m_socket;
    SOCKET clientSock=sp.rsock;
    sockaddr_in *clientAddr=sp.raddr;

    CRITICAL_SECTION * cs=s->cs;  //服务端类的临界区对象
    int rslt;
    char req[MAX_BUFFER_LEN+1]={0};//数据缓冲区,方便输出
    do
    {
        rslt=recv(clientSock,req,MAX_BUFFER_LEN,0);//接收数据
        if(rslt>0)
            break;
        char resp[MAX_BUFFER_LEN]={0}; //接受处理后的数据
        EnterCriticalSection(cs);
        rslt=s->procRequest(clientAddr,req,rslt,resp);//处理后返回数据的长度
        LeaveCriticalSection(cs);
        assert(rslt<=MAX_BUFFER_LEN);//不会超过MAX_BUFFER_LEN
        rslt=send(clientSock,resp,rslt,0); //发送tcp数据
    }
    while(rslt!=0||rslt!=SOCKET_ERROR);
    s->delClient(clientAddr);
    s->disConnect(clientAddr);//断开连接后处理
    return 0;
}
//线程处理函数使用传递的服务器对象指针pServer获取服务器socket,地址和临界区对象。
//  和客户端不同的是,服务接收发送数据使用的socket不是本地的socket而是客户端的socket!
//  为保证线程的并发控制,使用临界区的2个函数保证,中间的请求处理函数和UDP使用的相同。
//  另外,线程的退出表示客户端的连接断开,这里更新客户端列表并调用disconnect允许服务器做最后的处理,
//  和connect类似,这一对函数只针对tcp通信,对于UDP通信不存在调用关系。

Server::Server(void)
{
    cs=new CRITICAL_SECTION();
    InitializeCriticalSection(cs); //初始化临界区
      WSADATA wsa;
    int rslt=WSAStartup(WINSOCK_VERSION,&wsa);//加载WinSock DLL
    assert(rslt==0);
}
Server::~Server(void)
{
     for(list<sockaddr_in*>::iterator i=clientAddrs.begin();i!=clientAddrs.end();++i)//清空客户端地址结构
    {
        delete *i;
    }
    clientAddrs.clear();
    if(m_socket!=INVALID_SOCKET)
        closesocket(m_socket);//关闭服务器socket
    WSACleanup();//卸载WinSock DLL
    DeleteCriticalSection(cs);
    delete cs;
}
void Server::init(int inet_type,char* addr,unsigned short port)
{
    int rslt;
    m_type=inet_type;
    if(m_type==TCP_DATA)
        m_socket=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);//创建TCP套接字
    else if(m_type==UDP_DATA)
        m_socket=socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
    assert(m_socket!=INVALID_SOCKET);
    //设置服务端的地址
    serverAddr.sin_family=AF_INET;
    serverAddr.sin_addr.S_un.S_addr=inet_addr(addr);
    serverAddr.sin_port=htons(port);
    rslt=bind(m_socket,(sockaddr *)&serverAddr,sizeof(serverAddr)); //绑定地址和端口
    assert(rslt==0);
    if(m_type==TCP_DATA)  //如果是TCP,需要监听
    {
        rslt=listen(m_socket,MAX_TCP_CONNECT); //监听客户端连接
        assert(rslt!=0);
    }
}
void Server::start()
{
    int rslt;
    sockaddr_in client; //客户端地址结构
    int addrLen=sizeof(client);
    SOCKET clientSock; //客户端socket
    char  buff[MAX_BUFFER_LEN];  //UDP数据缓存
    while(true)
    {
        if(m_type==TCP_DATA)
        {
            clientSock=accept(m_socket,(sockaddr*)&client,&addrLen); //接受请求
            if(clientSock==INVALID_SOCKET)
                break;
            assert(clientSock!=INVALID_SOCKET);
            sockaddr_in *pc=addClient(client); //添加一个客户端
            connect(pc);  //连接处理函数
            SockParam sp(clientSock,pc,this); //参数结构
            HANDLE thread=CreateThread(NULL,0,threadProc,(LPVOID)&sp,0,NULL); //创建连接线程
            assert(thread!=NULL);
            CloseHandle(thread); //关闭线程
        }
        else if(m_type==UDP_DATA)
        {
            memset(buff,0,MAX_BUFFER_LEN);
            rslt=recvfrom(m_socket,buff,MAX_BUFFER_LEN,0,(sockaddr*)&client,&addrLen);
            assert(rslt>0);
            char resp[MAX_BUFFER_LEN]={0}; //接收处理后的函数
            rslt=procRequest(&client,buff,rslt,resp); //处理请求
            rslt=sendto(m_socket,resp,rslt,0,(sockaddr*)&client,addrLen); //发送UDP数据
        }
    }
}
void Server::connect(sockaddr_in * client)
{
    cout<<"客户端"<<getIP(client)<<"["<<getPort(client)<<"]"<<"连接。"<<endl;
}
int Server::procRequest(sockaddr_in*client,const char* req,int reqLen,char*resp)
{
    cout<<getIP(client)<<"["<<getPort(client)<<"]:"<<req<<endl;
    if(m_type==TCP_DATA)
        strcpy(resp,"TCP回复");
    else if(m_type==UDP_DATA)
        strcpy(resp,"UDP回复");
    return 10;
}
void Server::disConnect(sockaddr_in *client)
{
        cout<<"客户端"<<getIP(client)<<"["<<getPort(client)<<"]"<<"断开。"<<endl;
}
char* Server::getProto()
{
    if(m_type==TCP_DATA)
        return "TCP";
    else if(m_type==UDP_DATA)
        return "UDP";
    else
        return "";
}

char* Server::getIP(sockaddr_in*addr)
{
    if(addr==NULL)
        addr=&serverAddr;
    return inet_ntoa(addr->sin_addr);
}
unsigned short Server::getPort(sockaddr_in*addr)
{
    if(addr==NULL)
        addr=&serverAddr;
    return htons(addr->sin_port);
}
sockaddr_in * Server::addClient(sockaddr_in client)
{
    sockaddr_in *pc=new sockaddr_in(client);
    clientAddrs.push_back(pc);
    return pc;
}
void Server::delClient(sockaddr_in * client)
{
    assert(client!=NULL);
    delete client;
    clientAddrs.remove(client);
}

三、IP地址的表示形式与各个转换函数

计算机中不使用点分十进制保存IP地址,因为会浪费存储空间。
使用无符号长整型数来存储和表示IP地址。
分为网络字节序和主机字节序
1.网络字节序
TCP/IP规定,低位存储地址中保存数据的高位字节。这种存储顺序格式称为网络字节序。所以数据的传输顺序是由高位至低位进行的。
为使通信双方都能够理解数据分组所携带的原地址、目的地址、分组长度等二进制数据,不同类型(比如路由器、交换机或者计算机等)和不同操作系统的设备在发送每个分组数据之前,都必须将二进制数据转换为TCP/IP标准的网络字节顺序格式。
结构体in_addr(S_un)保存网络字节顺序格式的IP地址。
其中有一个变量 S_addr 以u_long变量表示的主机格式IP地址。

函数:

unsigned long inet_addr(const char*cp)  将点分法IP地址字符串转化为in_addr结构体中的IP地址格式

char *inet_ntoa(struct in_addr in) 将in_addr结构体中的IP地址转换为点分十进制格式的字符串

2.主机字节序
不同的主机在对IP地址进行存储时使用的格式也不同。
有4个函数可以实现主机字节序与网络字节序之间的转换

htonl()u_long类型的主机字节顺序格式IP地址转换为TCP/IP网络字节顺序格式
htons()u_short类型的主机字节顺序格式IP地址转换为TCP/IP网络字节顺序格式
ntohl()u_long类型的网络字节顺序IP地址转换为主机字节顺序格式
ntohs()u_short类型的TCP网络字节顺序IP地址转换为主机字节顺序格式

猜你喜欢

转载自blog.csdn.net/xiongluo0628/article/details/82314392