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. 包与包管理.md

    软件包:源码包   RPM包 二进制包 RPM包依赖性   模块依赖查询   www.rpmfind.net umount 解除CDROM挂载 mount 挂载 umount [/dev/device ...

  2. halcon案例学习之cbm_label_simple

    *cbm_label_simple 程序说明:*这个示例程序展示了如何使用基于组件的匹配来定位复合对象.在这种情况下,应该在图像中找到一个标签,用户既不知道其中的组件,也不知道它们之间的关系.因此,创 ...

  3. Token验证的流程及如何准确的判断一个数据的类型

    Token验证的流程: 1,客户端使用用户名跟密码请求登录:2,服务端收到请求,去验证用户名与密码:3,验证成功后,服务端会签发一个 Token,再把这个 Token 发送给客户端:4,客户端收到 T ...

  4. HTTP基础知识点小结

    什么是http协议? http,超文本传输协议是现在互联网应用最为广泛的协议,所有的www文件都必须遵循这个标准设计这个最初的目的是为了发布和接收HTML文件.http就是web通信的基础,就是为了能 ...

  5. MySQL学习Day01

    1.MySQL的层级关系 2.xampp的安装使用 如果之前安装过mysql那么就需要将原来的mysql完全卸载干净 1.卸载之前安装的MySQL 安装xampp需要先卸载之前的mysql,以及更改m ...

  6. 记一道C语言编程题(C语言学习笔记)

    题目如下 解答如下 #include <stdio.h> #include<math.h> double Mysqrt(double n) { return sqrt(n); ...

  7. CTFshow萌新赛-千字文

    打开靶机 下载完成后,为一张二维码图片 使用StegSolve 解出隐写图像 保存后使用PS或其他工具去除白边 然后使用脚本分割这个图像(25*25) from PIL import Image im ...

  8. firewalld原理和基础命令

    firewalld防火墙 Firewalld是什么? Firewalld提供了支持网络.防火墙定义网络看见以及接口安全等级的动态防火墙管理工具

  9. 使用memory_profiler异常

    在使用memory_profiler模块0.55.0版本执行命令诊断程序内存用量时,遇到下面错误: C:\Users\Chen\Desktop\python_doc\第四模块课件>python ...

  10. Jquery实现对Array数组实现类似Linq的Lambda表达式的Where方法筛选

    平时使用Linq,习惯了Lambda表达式,用着非常顺手,奈何在Jquery里面不能这样用,只能循环一个个判断.趁空闲时间找了找,自己写了这样的扩展方法.目前写出了三种方案,没有比较性能,觉得都可以用 ...