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. 【对线面试官】Java 反射&&动态代理

    // 抽象类,定义泛型<T> public abstract class BaseDao<T> { public BaseDao(){ Class clazz = this.g ...

  2. MySQL45讲笔记-事务隔离级别,为什么你改了数据我看不见

    简单来说,事务就是要保证一组数据库操作,要么全部成功,要么全部失败.在MySQL中,事务至此是在引擎层实现的,但并不是所有的MySQL引擎都支持事务,这也是MyISAM被InnoDB取代的原因之一. ...

  3. Linux 文件查看相关的一些命令

    文件压缩解压命令 # 解压 xxx.xz 并删除 xz -d test.tar.xz # 打包成 xxx.tar , 语法: tar -cvf 最后包名.tar ./要打包文件 ./要打包的文件 ta ...

  4. 【Linux】NFS搭建及使用详解

    环境:CentOS release 6.8 server  192.168.25.100 client1 192.168.25.101 client2 192.168.25.102 1.服务端操作 1 ...

  5. 【小菜学网络】交换机与MAC地址学习

    上一小节介绍了 集线器 ,一种工作于物理层的简单网络设备.由于集线器采用广播的方式中继.转发物理信号,传输效率受到极大制约. 精准转发 为了解决集线器工作效率低下的尴尬,我们需要设计一种更高级的网络设 ...

  6. Mybatis入门Demo(单表的增删改查)

    1.Mybatis 什么是Mybatis: mybatis是一个持久层框架,用java编写的 它封装了jdbc操作的很多细节,使开发者只需要关注sql语句本身,而无需关注注册驱动.创建连接等繁杂过程 ...

  7. django 组件 自定义过滤器 自定义标签 静态文件配置

    组件 将一些功能标签写在一个html文件里,这个文件作为一个组件,如果那个文件需要就直接拿过来使用即可: 这是title.html文件,写了一个导航栏,作为一个公用的组件 <div style= ...

  8. 本地Mac通过堡垒机代理实现跨堡垒机scp问题

    近日,公司在跳板机前架设了堡垒机,以防止ssh攻击,但这带来一个问题,我们平常直接ssh跳板机,可以直接使用scp来上传或下载跳板机数据到本地 架设堡垒之后经常使用的scp工具不好用了 于是本期就来解 ...

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

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

  10. SpringBoot 自动配置:Spring Data JPA

    前言 不知道从啥时候开始项目上就一直用MyBatis,其实我个人更新JPA些,因为JPA看起来OO的思想更强烈些,所以这才最近把JPA拿出来再看一看,使用起来也很简单,除了定义Entity实体外,声明 ...