草庐IT

C++学习之Socket

只要六元 2024-05-19 原文

Socket是什么

socket就是套接字的意思,用于描述地址和端口。应用程序通过socket向网络发出请求或者回应。

socket编程有三种:

  • 流式套接字(SOCK_STREAM)
  • 数据报套接字(SOCK_DGRAM)
  • 原始套接字(SOCK_RAW)

前两者较常用。基于TCP的socket编程是流式套接字。

服务端和客户端都做了什么

服务端:

建立socket,声明自身的port和IP,并绑定到socket,使用listen监听,然后不断用accept去查看是否有连接。如果有,捕获socket,并通过recv获取消息的内容,通信完成后调用closeSocket关闭这个对应accept到的socket。如果不需要等待任何客户端连接,那么用closeSocket直接关闭自身的socket。

转载至:https://blog.csdn.net/zahngjialiang/article/details/53929584

客户端:

建立socket,通过端口号和地址确定目标服务器,使用Connect连接到服务器,send发送消息,等待处理,通信完成后调用closeSocket关闭socket。

三次握手四次挥手是什么

第一次握手:Client将SYN(同步序列编号Synchronize Sequence Numbers)置1,随机产生一个初始序列号seq发送给Server,进入SYN_SENT状态;
第二次握手:Server收到Client的SYN=1之后,知道客户端请求建立连接,将自己的SYN置1,ACK(确认字符Acknowledge Character)置1,产生一个acknowledge number=sequence number+1,并随机产生一个自己的初始序列号,发送给客户端;进入SYN_RCVD状态;
第三次握手:客户端检查acknowledge number是否为序列号+1,ACK是否为1,检查正确之后将自己的ACK置为1,产生一个acknowledge number=服务器发的序列号+1,发送给服务器;进入ESTABLISHED状态;服务器检查ACK为1和acknowledge number为序列号+1之后,也进入ESTABLISHED状态;完成三次握手,连接建立。

第一次挥手:Client将FIN(finish)置为1,发送一个序列号seq给Server;进入FIN_WAIT_1状态;
第二次挥手:Server收到FIN之后,发送一个ACK=1,acknowledge number=收到的序列号+1;进入CLOSE_WAIT状态。此时客户端已经没有要发送的数据了,但仍可以接受服务器发来的数据。
第三次挥手:Server将FIN置1,发送一个序列号给Client;进入LAST_ACK状态;
第四次挥手:Client收到服务器的FIN后,进入TIME_WAIT状态;接着将ACK置1,发送一个acknowledge number=序列号+1给服务器;服务器收到后,确认acknowledge number后,变为CLOSED状态,不再向客户端发送数据。客户端等待2*MSL(报文段最长寿命)时间后,也进入CLOSED状态。完成四次挥手。

—————————————————————————————
原文链接:https://blog.csdn.net/weixin_41969690/article/details/106196702

Windows下的代码怎么写,参考:
https://blog.csdn.net/zahngjialiang/article/details/53929584
对于函数解释的很清晰。

另外目前的教程都是有服务端和客户端的代码的,刚开始看真的很蒙,不明白要怎么运行。其实他们是把两个代码分别编译成EXE后进行测试的。其实我们可以下载一个TCP调试助手,就可以只写一端的代码进行测试了。比如下图这个:

Socket代码解析

1. WSAStartup函数

使用Socket之前必须调用WSAStartup函数,此函数在应用程序中用来初始化Windows Sockets DLL,只有此函数调用成功后,应用程序才可以调用Windows SocketsDLL中的其他API函数,否则后面的任何函数都将调用失败。

函数的原型:

int WSAAPI WSAStartup(
  WORD      wVersionRequested,
  LPWSADATA lpWSAData
);

函数的使用:

WSAStartup(MAKEWORD(2, 2), &wsaData);//第一个参数是dll版本号,第二个参数是创建的socket对象

2. WSADATA

WSADATA,一种数据结构。
这个结构被用来存储被WSAStartup函数调用后返回的Windows Sockets数据。

3. MAKEWORD函数

Windows Sockets DLL期望调用者使用的Windows Sockets规范的版本。 高位字节存储副版本号, 低位字节存储主版本号,可以用WORD MAKEWORD(BYTE,BYTE ) 返回这个值,例如:MAKEWORD(1,1)

前面都做完了之后:

//需要确认是否初始化成功
    if (WSAStartup(sockVersion, &wsdata) != 0)
    {
        return 1;
    }

4. SOCKET函数

SOCKET socket(int af,int type,int protocol);
af:一个地址家族,通常为AF_INET(AF–ADDRESS FAMILY)
type:套接字类型,SOCK_STREAM表示创建面向流连接的套接字。为SOCK_DGRAM,表示创建面向无连接的数据包套接字。为SOCK_RAW,表示创建原始套接字
protocol:套接字所用协议,不指定可以设置为0 返回值就是一个socket

函数的使用:

	SOCKET serverSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (serverSocket == INVALID_SOCKET)
    {
        cout << "Socket error" << endl;
        return 1;
    }

5. sockaddr_in 结构体

成员变量如下:

	struct sockaddr_in{
    sa_family_t     sin_family;   //地址族(Address Family),也就是地址类型
    uint16_t        sin_port;     //16位的端口号
    struct in_addr  sin_addr;     //32位IP地址
    char            sin_zero[8];  //不使用,一般用0填充
};
  1. sin_family 和 socket() 的第一个参数的含义相同,取值也要保持一致。

  2. sin_prot 为端口号。uint16_t 的长度为两个字节,理论上端口号的取值范围为 0~65536,但 0~1023
    的端口一般由系统分配给特定的服务程序,例如 Web 服务的端口号为 80,FTP 服务的端口号为 21,所以我们的程序要尽量在
    1024~65536 之间分配端口号。 端口号需要用 htons() 函数转换,后面会讲解为什么。

  3. sin_addr 是 struct in_addr 结构体类型的变量,下面会详细讲解。

  4. sin_zero[8] 是多余的8个字节,没有用,一般使用 memset() 函数填充为 0。上面的代码中,先用 memset()
    将结构体的全部字节填充为 0,再给前3个成员赋值,剩下的 sin_zero 自然就是 0 了。

6. memset函数

复制字符 c(一个无符号字符)到参数 str 所指向的字符串的前 n 个字符。

void *memset(void *str, int c, size_t n)

函数的使用:

	sockaddr_in sockAddr;//创建套接字地址
	memset(&sockAddr, 0, sizeof(sockAddr));//用0填充每个字节
	sockAddr.sin_family = PF_INET;//使用PF_INET地址族,也就是IPv4
	sockAddr.sin_addr.s_addr = inet_addr("192.168.1.200");//具体的地址
	sockAddr.sin_port = htons(4196);//端口

7. in_addr 结构体(sin_addr)

sockaddr_in 的第3个成员是 in_addr 类型的结构体,该结构体只包含一个成员:

struct in_addr{
  in_addr_t  s_addr;  //32位的IP地址
};

in_addr_t 在头文件 <netinet/in.h> 中定义,等价于 unsigned long,长度为4个字节。也就是说,s_addr 是一个整数,而IP地址是一个字符串,所以需要 inet_addr() 函数进行转换。

8. bind()函数

函数声明:

int bind(SOCKET sock, const struct sockaddr *addr, int addrlen);

sock 为 socket 文件描述符,addr 为 sockaddr 结构体变量的指针,addrlen 为 addr 变量的大小,可由 sizeof() 计算得出。

函数的使用:

bind(servSock, (SOCKADDR*)&sockAddr, sizeof(SOCKADDR));

或者用下面的代码:

    if (bind(serverSocket, (sockaddr*)&sockAddr, sizeof(sockAddr)) == SOCKET_ERROR) {
        cout << "Bind error" << endl;
        return 1;
    }

9.listen()函数

函数原型为:

int listen(SOCKET sock, int backlog); 

sock 为需要进入监听状态的套接字,backlog 为请求队列的最大长度。
该函数只有服务端有,客户端没有

函数的使用:

    if (listen(serverSocket, 10) == SOCKET_ERROR) {
        cout << "Listen error" << endl;
        return 1;
    }

10.accept()函数

函数的原型:

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

函数说明:

accept()用来接受参数s 的socket 连线. 参数s 的socket 必需先经bind()、listen()函数处理过,
当有连线进来时accept()会返回一个新的socket 处理代码, 往后的数据传送与读取就是经由新的socket处理, 而原来参数s
的socket 能继续使用accept()来接受新的连线要求. 连线成功时, 参数addr 所指的结构会被系统填入远程主机的地址数据,
参数addrlen 为scokaddr 的结构长度

函数的使用:

    SOCKET clientSocket;
    sockaddr_in client_sin;
    int len = sizeof(client_sin);
    clientSocket = accept(serverSocket, (sockaddr*)&client_sin, &len);

11.send()函数

函数的原型:

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

send()用来将数据由指定的socket 传给对方主机. 参数s 为已建立好连接的socket. 参数msg 指向欲连线的数据内容, 参数len 则为数据长度. 参数flags 一般设0

11.closesocket()函数

函数的原型:

int closesocket(SOCKET s)

关闭套接字,释放与套接字关联的所有资源

函数的使用:

closesocket(clientSocket);
closesocket(serverSocket);

12.WSACleanup()函数

终止使用WinSock,释放为应用程序分配的相关资源

有关C++学习之Socket的更多相关文章

  1. ruby-openid:执行发现时未设置@socket - 2

    我在使用omniauth/openid时遇到了一些麻烦。在尝试进行身份验证时,我在日志中发现了这一点:OpenID::FetchingError:Errorfetchinghttps://www.google.com/accounts/o8/.well-known/host-meta?hd=profiles.google.com%2Fmy_username:undefinedmethod`io'fornil:NilClass重要的是undefinedmethodio'fornil:NilClass来自openid/fetchers.rb,在下面的代码片段中:moduleNetclass

  2. 已解决socket.timeout : The read operation timed out - 2

    已解决(pip安装模块超时,利用四种国内镜像源完美解决)WARENTING:Retrying(Retry(total=4,connect=None,read=None,redirect=None,status=None))afterconnectionbrokenby‘ConnectTimeoutError(pip._vendor.urllib3.connection.HTTPSConnectionobjectatOx00001D6OE4F4A940>,‘Connectiontopypi.orgtimedout.(connecttimeout=15)’)’':/simple/pip/socke

  3. ruby - Cucumber 测试无法启动,错误为 "Display socket is taken but lock file is missing.." - 2

    运行cucumber后bundleexeccucumberfeatures/emails.feature:20我遇到了错误Displaysocketistakenbutlockfileismissing-checktheHeadlesstroubleshootingguide(Headless::Exception)/Users/me/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/headless-2.2.0/lib/headless.rb:195:inensure_xvfb_is_running'/Users/me/.rbenv/ver

  4. ruby - Interface Builder 看不到 MacRuby 的 socket - 2

    我正在尝试使用XCode和InterfaceBuilder构建一个基本的helloworld应用程序。但是,在InterfaceBuilder中,我看不到我的socket可以连接起来。我转到对象检查器Pane的连接选项卡,它显示“NewReferencingOutlet”。我想知道我的代码是否有误。在这里classHelloWorldControllerattr_accessor:hello_label,:hello_button,:hellodefawakeFromNib@hello=trueenddefchangeLabel(sender)if@hello@hello_label.

  5. ruby - yarn 未初始化常量 Socket::SOL_TCP - 2

    我在这里尝试使用yarn,遇到了一个可能与ruby​​相关的问题。在执行任何yarn命令,我收到错误.../.rvm/gems/ruby-2.3.0/gems/yarn-0.1.1/lib/yarn/server.rb:14:in':uninitializedconstantSocket::SOL_TCP(NameError)错误堆栈:$yarn.../.rvm/gems/ruby-2.3.0/gems/yarn-0.1.1/lib/yarn/server.rb:14:in':uninitializedconstantSocket::SOL_TCP(NameError)Didyoume

  6. 多线程问题:为什么不应该使用多线程读写同一个socket连接? - 2

    问题的产生经典的单reactor多线程模式采用的是用主线程处理连接事件以及socket读写事件,业务逻辑的处理则是让线程池里的线程各自竞争处理。既然多线程这么方便,为什么不让线程池里的线程也参与到read和send这个过程中呢?在发送数据的过程中,即使TCP的发送缓存满了,我们也可以记录下当前成功发送了多少字节,然后再次注册一个EPOLLOUT事件,只需等待下次可写事件,继续让子线程发送数据即可,岂不是美哉?解释陈硕大佬的解释对于TCP,通常多线程读写同一个socket是错误的设计,因为有shortwrite的可能。假如你加锁,而又发生shortwrite,你是不是要一直等到整条消息发送完才解

  7. ruby-on-rails - postgresql 数据库错误 : Is the server running locally and accepting connections on Unix domain socket "/var/run/postgresql/.s.PGSQL.5432"? - 2

    当我运行rakedb:migrate或运行railss命令时,我得到同样的错误:Error:couldnotconnecttoserver:NosuchfileordirectoryIstheserverrunninglocallyandacceptingconnectionsonUnixdomainsocket"/var/run/postgresql/.s.PGSQL.5432"?当我尝试railss时,浏览器出现错误。这是我的database.ymldefault:&defaultadapter:postgresqlencoding:unicodepool:5development

  8. 嵌入式学习之QT学习----3 制作简单的QT界面(如:QQ登录界面) - 2

    1、创建一个QT工程newproject—>Application—>QtWidgetsApplication—>choose…(注意不要有中文路径)填写名称(我写的名称为class2)和创建路径(D:\qt\qt_demo\class2)—>填写类名,这里基类要选择“QWidget”,这样一个QT工程就创建好啦。qt的移植性非常强,一套代码我们不用修改太多,直接通用所有的平台。说明:QMainWindow:主窗口类,主窗口具有主菜单栏、工具栏和状态栏,类似于一般的应用程序的主窗口。QWidget:它是所有具有可视界面的基类,选择QWidget创建的界面对各种界面组件都可以支持。QDialog

  9. ruby-on-rails - 为什么我会出现间歇性的 Excon::Error::Socket: getaddrinfo: No address associated with hostname (SocketError)? - 2

    Rails4-Ruby2.2.2-亚马逊AWSS3-蜻蜓1.0.12-dragonfly-s3_data_store1.2-fog-aws0.10.0大约99%的时间我们没有问题。这个问题通常只发生在使用率很高的时候,但我注意到它也发生在几乎没有用户的时候。抛出错误的行:#excon/lib/excon/socket.rb#line100insidetheconnectionmethod.addrinfo=::Socket.getaddrinfo(*args)该错误在应用程序中无处不在。有时在没有远程连接时会出现错误。-我无法再验证这一点。我使用Rails记录器来捕获传入的参数,通过和

  10. javascript - chrome 扩展在后台页面和内容脚本下使用相同的 socket.io 连接 - 2

    我正在使用socket.io做一个chrome扩展,我有一个内容脚本可以保持连接到服务器以进行实时聊天,但我也想在后台页面中从服务器获取一些信息。它像这样分开工作很好在内容脚本中varsocket=io.connect('http://localhost:3700');socket.on('dosomething',function(){console.log("test");});在后台页面varsocket=io.connect('http://localhost:3700');socket.on('dosomething',function(){console.log("test

随机推荐