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. Python解释器和IPython

    目录 简介 Python解释器 IPython 魔法函数 运行和编辑 Debug History 运行系统命令 简介 今天给大家介绍一下Python的一个功能非常强大的解释器IPython.虽然Pyt ...

  2. 剑指offer 面试题7:重建二叉树

    题目描述 输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树.假设输入的前序遍历和中序遍历的结果中都不含重复的数字.例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7, ...

  3. Azure 存储简介

    Azure Storage Account(存储账户)包含所有Azure Storage的数据对象,包括Blob.Data Lake Gen2,File.Queue.Disk和Table等服务,该St ...

  4. 区间合并 C++

    #include <iostream> #include <vector> #include <algorithm> using namespace std; ty ...

  5. 通过show profile分析sql语句

    set profling=1; select count(*) from xuehao; show profiles; show profile for query 1; mysql> set ...

  6. 【Linux】md5sum 生产所有文件的md5值,并对照目标文件是否相同

    现在加入有很多很多文件需要测试md5,想看下是否都传输成功了,如何批量生成文件的md5并且逐条对照呢? 下面来简单介绍下 md5sum这个命令有一个选项"-c" 这个选项的意思是c ...

  7. Kafka 探险 - 生产者源码分析: 核心组件

    这个 Kafka 的专题,我会从系统整体架构,设计到代码落地.和大家一起杠源码,学技巧,涨知识.希望大家持续关注一起见证成长! 我相信:技术的道路,十年如一日!十年磨一剑! 往期文章 Kafka 探险 ...

  8. List使用Stream流进行集合Collection的各种运算汇总:对BigDecimal求和,某个字段的和、最大值、最小值、平均值,字段去重,过滤等

    写Java接口的朋友都知道,Java 8的更新,经常会用到过滤 list<Object> 里的数据,本文就对List使用Stream流进行集合Collection的各种运算做一个汇总! 优 ...

  9. 学习Java第一天

    public 保证类名和文件名一致 关键字字母全小写,编辑器中有颜色标记 null空常量不能打印 变量就是内存中的存储空间 计算机中最小的存储单元时字节(byte) //1字节(B) = 8位(bit ...

  10. file转化为binary对象发送给后台

    具体代码如下: function filechange(e) { var file = $('#filed').get(0).files[0]; var fileSize = file.size, f ...