Socket网络编程

Posted CPP编程客

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Socket网络编程相关的知识,希望对你有一定的参考价值。

首先来写点网络基础知识。


在网络通信中,两个进程分别位于不同的机子上,在互联网中,两台机子不能位于不同的网络,这些网络通过网络连接设备连接(网关,网桥,路由器)。

  1. 某一主机可与多个网络连接,必须指定该主机唯一的标识符。

  2. 每一台主机上的每一个进程应该有在该主机上的唯一标识符。




TCP/UDP协议:

在传输层上,有两大协议,分别是TCP协议和UDP协议。



UDP协议是一种无连接的传输层协议,提供面向事务的简单不可靠信息的传送。它效率高,但不能保证传送准确性,不可靠。


TCP端口和UDP端口各有65536个,一般小于1024的为保留端口,我们一般使用大于1024的端口。


常见的端口有:

TCP:

FTP       21

Telnet     23

SMTP     25

HTTP      80

POP3      110


UDP:

DNS        53

Tftp         69


大端和小端模式:

不同的计算机存放多字节值的顺序不同,分为大端模式和小端模式。




如:一个数为0x10203040,则:

大端模式存储为:10 20 30 40

小端模式存储为:40 30 20 10


要判断主机字节序,我这说两种方法:

方法一:

=================================

DWORD dwSmallNum = 0x01020304;

if(*(BYTE)&dwSmallNum == 0x04)

{

    std::cout << "小端模式" << std::endl;

}

else

{

    std::cout << "大端模式" << std::endl;

}


方法二:

===============================

DWORD dwSmallNum =  0x01020304;

if(dwSmallNum == htonl(dwSmallNum))

{

    std::cout << "大端模式" << std::endl;

}

else

{

    std::cout << "小端模式" << std::endl;

}


面向连接的协议:


面向连接使用的是TCP协议,服务端与客户端需要的Winsock函数如下:

服务端:

socket() --> bind() --> listen() --> accept() -->send()/recv() --> closesocket()

客户端:

socket --> connect() --> send()/recv() -->closesocket()


非面向连接的协议:

这种方式下,要发送数据,只需直接将要发送的数据传出即可,无需管是否送到。客户端在收到后,也不会有任何回应。怎么样,是不是感觉有点像寄信呢?


非面向连接的协议使用的是UDP协议,服务端与客户端的winsock函数如下:

服务端:

socket() --> bind() --> sendto() / recvfrom() --> closesocket()


客户端:

socket() --> sendto() / recvfrom() --> closesocket()


什么是套接字?

计算机本身是不能互相通讯的,而是计算机软件之间,在同一主机或不同主机之间进行通讯,数据之间的传输是在软件之间进行的。套接字,通俗地讲,它类似于文件指针,是一种用于软件之间数据收发的句柄。


一、Winsocket库的初始化:

int WSAStartup (

    WORD wVersionRequested,

    LPWSADATA lpWSAData

);

要使用Winsock的相关函数,我们先要对Winsock进行初始化,在使用完后,还要进行释放。


wVersionRequested:这个 参数表示winsock库的版本号,一般为2.2。

lpWSAData:这是一个指向WSADATA的指针,返回0为成功。


释放函数:

int  WSACleanup (void);


二、套接字的创建与关闭:

SOCKET socket (

    int af,         

    int type,       

    int protocol  

);

创建一个套接字,也就是创建一个网络通讯句柄的意思。socket只是返回你以后在系统调用中可能用到的socket描述符,错误时返回-1。使用WSAGetLastError()可以查看错误原因。




type:这个参数用于告诉内核使用哪种套接字描述符的类型。通常有3个值:

SOCK_STREAM 流套接字

SOCK_DGRAM 数据包套接字

SOCK_RAW  原始协议接口


protocol:这个参数用来指定应用程序所使用的通信协议。也有3个值。

IPPROTO_TCP

IPPROTO_UDP

IPPROTO_ICMP


这三个值和第二个参数上的三个值是相对应的。

第二个参数使用SOCK_STREAM,则第三个参数为IPPROTO_TCP。

第二个参数使用SOCK_DGRAM,则第三个参数为IPPROTO_UDP。

(注:前面两种情况下,第三个参数可以默认为0)

第二个参数使用SOCK_ICMP,则第三个参数 为IPPROTO_ICMP。


int bind (

    SOCKET s,

    const struct sockaddr FAR* name,

    int namelen

);




s:第一个参数是新创建的套接字描述符,就是刚才用socket创建的那个。

namelen:name的大小

struct sockaddr {

    u_short  sa_family;

    char sa_data[14];

}; 


这个结构共有16个字节,在它之前还要使用一个结构体,定义如下:

struct sockaddr_in{

    short sin_family;

    unsigned short  sin_port;

    struct in_addr sin_addr;

    char sin_zero[8];

};



在sockaddr_in中,还有一个结构体in_addr:

struct in_addr {
    union {
            struct { u_char s_b1,s_b2,s_b3,s_b4; } S_un_b;
            struct { u_short s_w1,s_w2; } S_un_w;
            u_long S_addr;
    } S_un;


unsigned long inet_addr (
  const char FAR * cp  
);


char FAR * inet_ntoa (
  struct in_addr in  
);

使用这个函数把unsigned long类型的数据转换为点分十进制表示。


四、监听

int listen (
  SOCKET s,    
  int backlog  
);

s:指定要监听的套接字。

backlog:允许进入请求的最大连接数,winsock中,一般由此宏表示



五、接受连接请求

SOCKET accept (
  SOCKET s,                   
  struct sockaddr FAR* addr,  
  int FAR* addrlen            
);


这个函数用于从连接请求的队列中获取连接信息。


s:要监听的套接字描述符。

addrlen:指向int,传入sockaddr的大小。


六、连接

int connect (
  SOCKET s,                          
  const struct sockaddr FAR*  name,  
  int namelen                        
);


接受请求了,就可以进行连接了。


s: 套接字描述符。

namelen:指定sockaddr的长度。


七、通信

一切就绪,现在便可以进行通信了。

int send (
  SOCKET s,              
  const char FAR * buf,  
  int len,               
  int flags              
);


这个用于发送消息。


s:accept()返回的套接字描述符。

buf:发送消息的缓冲区。

len:buf的长度

flags:通常为0.



int recv (
  SOCKET s,       
  char FAR* buf,  
  int len,        
  int flags       
);


这个用于接收消息,其参数和send()使用方法是一样的。



下面这是UDP的,在UDP中,不需要listen()和accept(),客户端也不需要connect(),通信函数变为这样:

int sendto (
  SOCKET s,                        
  const char FAR * buf,            
  int len,                         
  int flags,                       
  const struct sockaddr FAR * to,  
  int tolen                        
);


s:套接字描述符。

buf:发送数据的缓冲区。

len:buf的长度。

flags:0

tolen:to的长度。



int recvfrom (  
    SOCKET s,                     
    char FAR* buf,              
    int len,                      
    int flags,                  
    struct sockaddr FAR* from,    
    int FAR* fromlen            
);


用法和sendto相同。



现在就来写一个简单的TCP通信:

服务端:

===================================

SocketServer.h

==================================

#include <iostream>
#include <string>
#include <WinSock2.h>
#pragma comment(lib, "ws2_32")

class SocketServer
{
public:
    SocketServer();   //初始化,创建,分配
    ~SocketServer();  //释放
    void Listen();    //监听
    void Accept();    //接受
    void SendMsg(const char* message);      //发送
    void RecvMsg();   //接收

private:
    void PrintError(const char* err) const;

private:
    WSADATA m_wWsaData;
    SOCKET m_hServSock, m_hClntSock;
    SOCKADDR_IN m_sServAddr, m_sClntAddr;
    int m_iClntAddr;
    char m_sMessage[MAXBYTE];
};



SocketServer.cpp

==========================


使用:

======================

#include <iostream>
#include "SocketServer.h"

int main()
{
    char msg[] = "Hello Client";
    SocketServer sockServ;
    sockServ.Listen();
    sockServ.Accept();
    sockServ.SendMsg(msg);
    sockServ.RecvMsg();

    return 0;
}





客户端:

=======================

SocketClnt.h

=====================
#include <iostream>
#include <Winsock2.h>
#pragma comment(lib, "ws2_32")

class SocketClnt
{
public:
    SocketClnt();
    ~SocketClnt();
    void Connect();
    void SendMsg(const char *message);
    void RecvMsg();

private:
    void PrintError(const char *err) const;

private:
    WSADATA m_wWsaData;
    SOCKET m_hSocket;
    SOCKADDR_IN m_sServAddr;
    char m_sMessage[MAXBYTE];
};



SocketClnt.cpp

============================

#include "SocketClnt.h"
#pragma warning(disable:4996)

SocketClnt::SocketClnt()
{
    //初始化套接字
    if (WSAStartup(MAKEWORD(2, 2), &m_wWsaData) != 0)
    {
        PrintError("WSAStartup()");
    }

    //创建套接字
    m_hSocket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (INVALID_SOCKET == m_hSocket)
    {
        PrintError("socket()");
    }

    memset(&m_sServAddr, 0, sizeof(m_sServAddr));
    m_sServAddr.sin_family = AF_INET;
    m_sServAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
    m_sServAddr.sin_port = htons(8888);
}


SocketClnt::~SocketClnt()
{
    closesocket(m_hSocket);
    WSACleanup();
}

void SocketClnt::Connect()
{
    //通过套接字向服务端发出链接请求
    if (connect(m_hSocket, (sockaddr*)&m_sServAddr, sizeof(m_sServAddr)))
    {
        PrintError("connect()");
    }
}

void SocketClnt::SendMsg(const char *message)
{
    strcpy(m_sMessage, message);

    //发送数据到服务端
    send(m_hSocket, m_sMessage, sizeof(m_sMessage) + sizeof(char), 0);
}

void SocketClnt::RecvMsg()
{
    //接受服务端发来的数据
    recv(m_hSocket, m_sMessage, sizeof(m_sMessage) + sizeof(char), 0);
    std::cout << "Message from server:" << m_sMessage << std::endl;
}

void SocketClnt::PrintError(const char* err) const
{
    std::cerr << err << " error!" << std::endl;
    std::cin.get();
    exit(1);
}


使用:

==============================

#include <iostream>
#include "SocketClnt.h"

int main()
{
    char msg[] = "Hello Server";
    SocketClnt sockClnt;
    sockClnt.Connect();
    sockClnt.RecvMsg();
    sockClnt.SendMsg(msg);

    return 0;
}



运行如下:


好了,今天就写这么多了,慢慢会写些有趣的东西的。

以上是关于Socket网络编程的主要内容,如果未能解决你的问题,请参考以下文章

VSCode自定义代码片段——JS中的面向对象编程

VSCode自定义代码片段9——JS中的面向对象编程

VSCode自定义代码片段14——Vue的axios网络请求封装

VSCode自定义代码片段14——Vue的axios网络请求封装

VSCode自定义代码片段14——Vue的axios网络请求封装

Python--网络编程-----socket代码实例