Socket 入门

前置知识 :计算机网络基础(TCP/IP四层模型)

Socket 原意是“插座”,在计算机通信领域被翻译为“套接字”,以\(\{IP:Port\}\) 的形式表示。

Windows 与Linux 的Socket编程有一些小的区别,由于Unix系统中一切都是文件,网络连接也不例外,只要网络联通,一切的操作都是在操作文件,每个文件都会有一个文件描述符。与之相对应的,Windows下面的文件描述符就是句柄,利用句柄来操作Socket。本文总结了Windows的Socket编程

最常用的Socket主要是两种:

  1. 流式套接字(TCP) : SOCK_STREAM

    1. 数据在传输时不会丢失(可靠的)
    2. 数据按照顺序传输(先发送的先到达,后发送的后到达)
    3. 数据的发送和接受不是同步的(TCP以字节流的形式发送的特性)
  2. 数据包格式套接字(UDP) : SOCK_DGRAM
    1. 强调速度快而非顺序
    2. 传输的信息可能丢包也能损毁
    3. 限制每次传输的大小
    4. 数据的发送和接受是同步的

以上两种套接字的特性可以参考TCP协议与UDP协议

使用Socket通信的步骤

Server

  1. socket() 创建套接字
  2. bind() 套接字与\(\{IP:Port\}\) 进行绑定
  3. listen() 监听
  4. accept() 接受链接请求
  5. recv() 接受消息
  6. close() 关闭套接字

Client

  1. socket() 创建套接字
  2. connect() 向服务器发起连接
  3. send() 向服务器发送消息
  4. close() 关闭套接字

创建socket之前需要掌握的知识

动态库的加载

Windows 下的 socket 程序依赖 Winsock.dll 或 ws2_32.dll,必须提前加载。

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

动态库的初始化

在使用Socket依赖的DDL之前,要先对其初始化,所用的函数是WSAStartup(),含义是\(Windows~Socket~API~Start~Up\), 要用该函数来指明WinSock的版本。

int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData);

WORD 表示是一个字,两个字节16bit。wVersionRequested参数表示WinSock的版本号,我们可以用宏函数MAKEWORD 直接转换传入参数(版本号一般是最新的2.2)。

WSADATA 结构体存放有关动态库的信息,在函数执行完之后,会将信息放入到 lpWSAData

LP 表示指针 long point

综上,以下两行代码即可实现动态库的初始化:

WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);

创建套接字

Windows 下使用socket函数来创建套接字,返回SOCKET类型句柄

SOCKET socket(int af, int type, int protocol);
  1. af 地址族(Address Family)

    AF_INET,AF_INET6分别表示IPv4和IPv6,除了AF,也可以用PF(Protocol Family),BF_INET,BF_INET6

  2. type 套接字类型: SOCK_STREAM,SOCK_DGRAM。流式套接字以及数据包式套接字

  3. protocol 传输协议:IPPROTO_TCP, IPPROTO_UDP

Example:

SOCKET servSock = socket(AF_INET, SOCK_STREAM, 0);

第三个参数一般可以是0,前提是前面两个参数可以自行推断出第三个参数

绑定套接字 & 客户端与服务端连接

socket() 可以用来创建套接字,服务器要将套接字与\(\{IP:Port\}\) 进行绑定,之后,流经该指定IP的端口数据将由套接字处理。

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

sock 为SOCKET句柄,addr 为 sockaddr 结构体指针变量,addrlen 为 addr 变量大小,可以用 sizeof 运算符计算得到。

这里有一个重要的结构体 sockaddr, 该结构体内保存着IP地址类型以及地址和端口信息。由于不同的IP地址(IPv4 or IPv6),长度并不相同,为了方便传入地址信息,需要利用另外两个结构体。

Example:bind

// sockaddr_in 结构体用来存放地址信息
sockaddr_in sockAddr;
memset(&sockAddr, 0, sizeof(sockAddr)); //每个字节都用0填充
sockAddr.sin_family = PF_INET; //使用IPv4地址
sockAddr.sin_addr.s_addr = inet_addr("127.0.0.1"); //具体的IP地址,由于127.0.0.1并不好存储,所以需要inet_addr进行转换
sockAddr.sin_port = htons(1234); //端口,需要用htons函数转换 // 将sockaddr_in* 类型强制转换为SOCKADDR* 类型
bind(servSock, (SOCKADDR*)&sockAddr, sizeof(SOCKADDR));

上面的例子与IPv4的地址进行绑定,如果使用IPv6地址,需要将sockaddr_in 结构体换为:sockaddr_in6

sockAddr.sin_addr.s_addr 保存了IP地址,为什么不直接存在sin_addr里面,而要把s_addr装在sin_addr结构体里面? 大概是历史的原因

至此,服务端将socket与\(\{IP:Port\}\) 绑定成功,客户端可以通过connect 函数向服务端发起连接请求

int connect(SOCKET sock, const struct sockaddr *serv_addr, int addrlen);

Example: connect

//向服务器发起请求
sockaddr_in sockAddr; memset(&sockAddr, 0, sizeof(sockAddr));
sockAddr.sin_family = AF_INET;
sockAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
sockAddr.sin_port = htons(1234); connect(sock, (SOCKADDR*)&sockAddr, sizeof(SOCKADDR));

接听与响应

服务端使用 listen 函数使套接字进入监听状态,在调用accept函数,以便随时响应连接请求

int listen(SOCKET sock, int backlog);
SOCKET accept(SOCKET sock, struct sockaddr *addr, int *addrlen);

backlog 为请求队列的长度,套接字处理客户端请求时,新的请求要放入到缓存队列中,该参数限制了队列的长度,如果设置为 SOMAXCONN ,则交给系统决定该长度

注意:listen() 只是让套接字处于监听状态,并没有接收请求。接收请求需要使用 accept() 函数。

accept 函数中的addr保存了客户端的IP和端口信息

Example: listen & accept

//进入监听状态
listen(servSock, 20); //接收客户端请求
SOCKADDR clntAddr;
int nSize = sizeof(SOCKADDR);
SOCKET clntSock = accept(servSock, (SOCKADDR*)&clntAddr, &nSize);

数据的接受与发送

!!! 终于到了有趣的地方了,前面所做的一切都是为了现在

发送数据使用:

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

接受数据使用:

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

这两个函数很简单,参数一目了然(最后的flags先不必管),buf表示要发送的字符串信息,len表示其长度。sock表示对方的socket句柄

服务器在accept到客户端的请求后,会获取到客户端的socket句柄,所以如果在服务器端向客户端发送数据或者接收数据就用该句柄

客户端在connect之前,创建的socket正是服务器的\(\{IP:Port\}\),利用该SOCKET句柄向服务器发送数据或者接收数据

实现回声客户端

Server端:

#include <stdio.h>
#include <winsock2.h>
#pragma comment (lib, "ws2_32.lib") //加载 ws2_32.dll
#define BUF_SIZE 100 int main() { WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData); //创建套接字
SOCKET servSock = socket(AF_INET, SOCK_STREAM, 0); //绑定套接字
sockaddr_in sockAddr;
memset(&sockAddr, 0, sizeof(sockAddr)); //每个字节都用0填充
sockAddr.sin_family = PF_INET; //使用IPv4地址
sockAddr.sin_addr.s_addr = inet_addr("127.0.0.1"); //具体的IP地址
sockAddr.sin_port = htons(1234); //端口 bind(servSock, (SOCKADDR*)&sockAddr, sizeof(SOCKADDR)); //进入监听状态
listen(servSock, 20); //接收客户端请求
SOCKADDR clntAddr;
int nSize = sizeof(SOCKADDR);
SOCKET clntSock = accept(servSock, (SOCKADDR*)&clntAddr, &nSize); while (true) {
char buffer[BUF_SIZE];
int strLen = recv(clntSock, buffer, BUF_SIZE, 0);
buffer[strLen] = '\0';
if (strcmp(buffer, "quit") == 0) break;
send(clntSock, buffer, strLen, 0);
} //关闭套接字
closesocket(clntSock);
closesocket(servSock);
//终止 DLL 的使用
WSACleanup();
return 0;
}

Client 端

#include <stdio.h>
#include <stdlib.h>
#include <WinSock2.h>
#pragma comment(lib, "ws2_32.lib") //加载 ws2_32.dll #define BUF_SIZE 100 int main() {
//初始化DLL Windows Socket API DATA
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData); //创建套接字
SOCKET sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); //向服务器发起请求
sockaddr_in sockAddr; memset(&sockAddr, 0, sizeof(sockAddr)); //每个字节都用0填充
sockAddr.sin_family = PF_INET;
sockAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
sockAddr.sin_port = htons(1234);
connect(sock, (SOCKADDR*)&sockAddr, sizeof(SOCKADDR)); while (true) {
char bufSend[BUF_SIZE] = { 0 };
printf("Input a string: ");
scanf("%s", bufSend);
send(sock, bufSend, strlen(bufSend), 0);
if (strcmp(bufSend, "quit") == 0) {
break;
}
char bufRecv[BUF_SIZE] = { 0 };
recv(sock, bufRecv, BUF_SIZE, 0); //输出接收到的数据
printf("Message form server: %s\n", bufRecv);
} //关闭套接字
closesocket(sock); //终止使用 DLL
WSACleanup(); system("pause");
return 0;
}

参考资料:http://c.biancheng.net/socket/

C++ Socket 入门的更多相关文章

  1. 从Socket入门到BIO,NIO,multiplexing,AIO

    Socket入门 最简单的Server端读取Client端内容的demo public class Server { public static void main(String [] args) t ...

  2. 从Socket入门到BIO,PIO,NIO,multiplexing,AIO(未完待续)

    Socket入门 最简单的Server端读取Client端内容的demo public class Server { public static void main(String [] args) t ...

  3. python笔记-9(subprocess模块、面向对象、socket入门)

    一.subprocess 模块 1.了解os.system()与os.popen的区别及不足 1.1 os.system()可以执行系统指令,将结果直接输出到屏幕,同时可以将指令是否执行成功的状态赋值 ...

  4. Socket 入门- 客户端回射程序

    结果输出:------------------------------------------------------客户端:xx@xxxxxx:~/Public/C$ ./postBackCli.o ...

  5. Socket入门Demo

    一.简单介绍下Socket编程    申明:.net网络编程 1)什么是Socket编程? Socket编程就是常说的网络通讯编程,套接字编程.一般应用于软件聊天通讯,以及软件与硬件之间的通讯. 通熟 ...

  6. socket入门基础

    #/usr/bin/python #-*- coding:utf-8 -*- import socket ip_port = ('127.0.0.1',111) #创建socket对象 sk = so ...

  7. socket入门

    结构图如下 一个套接字就是socket模块中的socket类的一个实例.实例化时需要3个参数 地址族:默认(socket.AF_INET) 流:默认(socket.SOCK_STREAM)  或数据报 ...

  8. Socket入门-获取服务器时间实例

    daytimetcpsrv.c #include <stdio.h> #include <string.h> #include <stdlib.h> #includ ...

  9. C# Socket 入门4 UPD 发送结构体(转)

    今天我们来学 socket  发送结构体 1. 先看要发送的结构体 using System; using System.Collections.Generic; using System.Text; ...

随机推荐

  1. 直播预告:Quadro RTX显卡助力Twinmotion在建筑表现领域火力全开

    新年伊始,泛CG继续起航! 2021年首期泛CG分享会 我们邀请了两位业界大咖一起分享 NVIDIA GPU实时渲染在建筑可视化领域的应用 新的一年,继续相约! 1.嘉宾介绍 魏老师从事设计可视化工作 ...

  2. kubernets之服务发现

    一  服务与pod的发现 1.1  服务发现pod是很显而易见的事情,通过简称pod的标签是否和服务的标签一致即可,但是pod是如何发现服务的呢?这个问题其实感觉比较多余,但是接下来你就可能不这么想了 ...

  3. P1220 关路灯(区间规划)

    题目描述 某一村庄在一条路线上安装了n盏路灯,每盏灯的功率有大有小(即同一段时间内消耗的电量有多有少).老张就住在这条路中间某一路灯旁,他有一项工作就是每天早上天亮时一盏一盏地关掉这些路灯. 为了给村 ...

  4. [Usaco2009 Feb]Revamping Trails 道路升级

    题目描述 每天,农夫John需要经过一些道路去检查牛棚N里面的牛. 农场上有M(1<=M<=50,000)条双向泥土道路,编号为1..M. 道路i连接牛棚P1_i和P2_i (1 < ...

  5. Spring Bean详解

    Spring Bean 在Spring的应用中,Spring IoC容器可以创建.装配和配置应用组件对象,这里的组件对象称为Bean. Bean的配置 Spring可以看作一个大型工厂,用于生产和管理 ...

  6. 【Android】编译报错 Annotation processors must be explicitly declared now 解决方案

    问题 在网上下载一个demo,因为版本久远,里面添加了本地 Butter Knife 的jar包,在编译时报错 Annotation processors must be explicitly dec ...

  7. 网络流量预测入门(一)之RNN 介绍

    目录 网络流量预测入门(一)之RNN 介绍 RNN简介 RNN 结构 RNN原理 结构原理 损失函数$E$ 反向传播 总结 参考 网络流量预测入门(一)之RNN 介绍 了解RNN之前,神经网络的知识是 ...

  8. uni-app开发经验分享三: Vuex实现登录和用户信息留存

    在做用户登录的过程中,其实最重要的是登录成功后的数据要怎么储存,储存到哪里,这里我分享一个利用vuex来实现用户登录和用户数据留存的方法 vuex代码如下: //引入vue和vuex import V ...

  9. MATLAB图像处理_Bayer图像处理 & RGB Bayer Color分析

    Bayer图像处理   Bayer是相机内部的原始图片, 一般后缀名为.raw. 很多软件都可以查看, 比如PS. 我们相机拍照下来存储在存储卡上的.jpeg或其它格式的图片, 都是从.raw格式转化 ...

  10. ryu安装

    $ python3 -V Python 3.7.2 $ git clone https://github.com/faucetsdn/ryu.git $ cd ryu $ sudo pip3 inst ...