转载:https://blog.csdn.net/yao_hou/article/details/91400832  https://blog.csdn.net/Ctrl_qun/article/list/2?

一、什么是Socket
         socket即套接字,用于描述地址和端口,是一个通信链的句柄。应用程序通过socket向网络发出请求或者回应。

sockets(套接字)编程有三种,流式套接字(SOCK_STREAM),数据报套接字(SOCK_DGRAM),原始套接字(SOCK_RAW);前两种较常用。基于TCP的socket编程是采用的流式套接字。

(1)SOCK_STREAM:字节流传输,表示面向连接的数据传输方式。数据可以准确无误地到达另一台计算机,如果损坏或丢失,可以重新发送,但效率相对较慢。常用的HTTP协议就使用SOCK_STREAM传输数据,因为要确保数据的正确性,否则网页不能正常解析。

(2)SOCK_DGRAM:数据报传输,表示无连接的数据传输方式。计算机只管传输数据,不作数据校验,如果数据在传输中损坏,或者没有到达另一台计算机,是没有办法补救的。也就是说,数据错了就错了,无法重传。因为SOCK_DGRAM所做的校验工作少,所以效率比SOCK_STREAM高。

QQ视频聊天和语音聊天就使用SOCK_DGRAM传输数据,因为首先要保证通信的效率,尽量减小延迟,而数据的正确性是次要的,即使丢失很小的一部分数据,视频和音频也可以正常解析,最多出现噪点或杂音,不会对通信质量有实质的影响。

注意:SOCK_DGRAM没有想象中的糟糕,不会频繁的丢失数据,数据错读只是小概率事件。

有可能多种协议使用同一种数据传输方式,所以在socket编程中,需要同时指明数据传输方式和协议。

二、客户端/服务端模式:
       在TCP/IP网络应用中,通信的两个进程相互作用的主要模式是客户/服务器模式,即客户端向服务器发出请求,服务器接收请求后,提供相应的服务。客户/服务器模式的建立基于以下两点:

(1)建立网络的起因是网络中软硬件资源、运算能力和信息不均等,需要共享,从而就让拥有众多资源的主机提供服务,资源较少的客户请求服务这一非对等作用。

(2)网间进程通信完全是异步的,相互通信的进程间既不存在父子关系,又不共享内存缓冲区。

因此需要一种机制为希望通信的进程间建立联系,为二者的数据交换提供同步,这就是基于客户/服务端模式的TCP/IP。

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

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

三、TCP 编程步骤
(1)服务端
1、加载套接字库,创建套接字(WSAStartup()/socket());

2、绑定套接字到一个IP地址和一个端口上(bind());

3、将套接字设置为监听模式等待连接请求(listen());

4、请求到来后,接受连接请求,返回一个新的对应于此次连接的套接字(accept());

5、用返回的套接字和客户端进行通信(send()/recv());

6、返回,等待另一个连接请求;

7、关闭套接字,关闭加载的套接字库(closesocket()/WSACleanup());

(2)客户端
1、加载套接字库,创建套接字(WSAStartup()/socket());

2、向服务器发出连接请求(connect());

3、和服务器进行通信(send()/recv());

4、关闭套接字,关闭加载的套接字库(closesocket()/WSACleanup());

代码演示,基于TCP的C/S模型

 /*
TCP服务端程序
*/ #include "pch.h"
#include <iostream>
#include <winsock2.h> #pragma comment(lib,"ws2_32.lib")
using namespace std; int main(int argc, char* argv[])
{
//初始化WSA
WORD sockVersion = MAKEWORD(, );
WSADATA wsaData;
if (WSAStartup(sockVersion, &wsaData) != )
{
return ;
} //创建套接字
SOCKET slisten = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (slisten == INVALID_SOCKET)
{
cout << "create socket error !" << endl;
return ;
} //绑定IP和端口
sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons();
sin.sin_addr.S_un.S_addr = INADDR_ANY;
if (bind(slisten, (LPSOCKADDR)&sin, sizeof(sin)) == SOCKET_ERROR)
{
cout << "bind error !" << endl;
} //开始监听
if (listen(slisten, ) == SOCKET_ERROR)
{
cout << "listen error !" << endl;
return ;
} //循环接收数据
SOCKET sClient;
sockaddr_in remoteAddr;
int nAddrlen = sizeof(remoteAddr);
char revData[];
while (true)
{
cout << "阻塞。。。。等待连接。。。" << endl;
sClient = accept(slisten, (SOCKADDR *)&remoteAddr, &nAddrlen);
if (sClient == INVALID_SOCKET)
{
cout << "accept error !" << endl;
continue;
} cout << "接受到一个连接:" << inet_ntoa(remoteAddr.sin_addr) << endl; //接收数据
int ret = recv(sClient, revData, , );
if (ret > )
{
revData[ret] = 0x00;
printf(revData);
} //发送数据
const char * sendData = "你好,TCP客户端!\n";
send(sClient, sendData, strlen(sendData), );
closesocket(sClient);
} closesocket(slisten);
WSACleanup();
return ;
}
 /*
TCP客户端代码
*/ #include "stdafx.h"
#include<winsock2.h>
#include<iostream>
#include<string> using namespace std;
#pragma comment(lib, "ws2_32.lib") int main()
{
WORD sockVersion = MAKEWORD(, );
WSADATA data;
if (WSAStartup(sockVersion, &data) != )
{
return ;
} while (true)
{
SOCKET sclient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sclient == INVALID_SOCKET)
{
cout << "invalid socket!" << endl;
return ;
} sockaddr_in serAddr;
serAddr.sin_family = AF_INET;
serAddr.sin_port = htons();
serAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
if (connect(sclient, (sockaddr *)&serAddr, sizeof(serAddr)) == SOCKET_ERROR)
{
//连接失败
cout << "connect error !" << endl;
closesocket(sclient);
return ;
} string data;
cin >> data;
const char * sendData;
sendData = data.c_str(); //string转const char* /*
send()用来将数据由指定的socket传给对方主机
int send(int s, const void * msg, int len, unsigned int flags)
s为已建立好连接的socket,msg指向数据内容,len则为数据长度,参数flags一般设0
成功则返回实际传送出去的字符数,失败返回-1,错误原因存于error
*/
send(sclient, sendData, strlen(sendData), ); char recData[];
int ret = recv(sclient, recData, , );
if (ret>)
{
recData[ret] = 0x00;
cout << recData << endl;
}
closesocket(sclient);
} WSACleanup(); system("pause");
return ;
}

说明:服务端的accept函数是阻塞的,如果客户端不发起连接会一直阻塞。

下面是UDP的C/S模型代码

UDP服务端

 #include <stdio.h>
#include <winsock2.h> #pragma comment(lib,"ws2_32.lib") int main(int argc, char* argv[])
{
WSADATA wsaData;
WORD sockVersion = MAKEWORD(,);
if(WSAStartup(sockVersion, &wsaData) != )
{
return ;
} SOCKET serSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if(serSocket == INVALID_SOCKET)
{
printf("socket error !");
return ;
} sockaddr_in serAddr;
serAddr.sin_family = AF_INET;
serAddr.sin_port = htons();
serAddr.sin_addr.S_un.S_addr = INADDR_ANY;
if(bind(serSocket, (sockaddr *)&serAddr, sizeof(serAddr)) ==SOCKET_ERROR)
{
printf("bind error !");
closesocket(serSocket);
return ;
} sockaddr_in remoteAddr;
int nAddrLen = sizeof(remoteAddr);
while (true)
{
char recvData[];
int ret = recvfrom(serSocket, recvData, , , (sockaddr*)&remoteAddr, &nAddrLen);
if (ret > )
{
recvData[ret] = 0x00;
printf("接受到一个连接:%s \r\n",inet_ntoa(remoteAddr.sin_addr));
printf(recvData);
} const char * sendData = "一个来自服务端的UDP数据包\n";
sendto(serSocket, sendData,strlen(sendData), , (sockaddr *)&remoteAddr, nAddrLen); }
closesocket(serSocket);
WSACleanup();
return ;
}

UDP客户端

 #include <stdio.h>
#include <winsock2.h> #pragma comment(lib,"ws2_32.lib") int main(int argc, char* argv[])
{
WORD socketVersion = MAKEWORD(,);
WSADATA wsaData;
if(WSAStartup(socketVersion, &wsaData) != )
{
return ;
}
SOCKET sclient = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons();
sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
int len = sizeof(sin); const char * sendData = "来自客户端的数据包.\n";
sendto(sclient, sendData, strlen(sendData), , (sockaddr *)&sin,len); char recvData[];
int ret = recvfrom(sclient, recvData, , , (sockaddr *)&sin,&len);
if(ret > )
{
recvData[ret] = 0x00;
printf(recvData);
} closesocket(sclient);
WSACleanup();
return ;
}

四、函数解析

SOCKET socket(int af, int type, int protocol);

Windows 不把套接字作为普通文件对待,而是返回 SOCKET 类型的句柄。

1) af 为地址族(Address Family),也就是 IP 地址类型,常用的有 AF_INET 和 AF_INET6。AF 是“Address Family”的简写,INET是“Inetnet”的简写。AF_INET 表示 IPv4 地址,例如 127.0.0.1;AF_INET6 表示 IPv6 地址,例如 1030::C9B4:FF12:48AA:1A2B。

127.0.0.1,它是一个特殊IP地址,表示本机地址。

也可以使用PF前缀,PF是“Protocol Family”的简写,它和AF是一样的。例如,PF_INET 等价于 AF_INET,PF_INET6 等价于 AF_INET6。

2) type 为数据传输方式,常用的有 SOCK_STREAM 和 SOCK_DGRAM

3) protocol 表示传输协议,常用的有 IPPROTO_TCP 和 IPPTOTO_UDP,分别表示 TCP 传输协议和 UDP 传输协议。

一般情况下有了 af 和 type 两个参数就可以创建套接字了,操作系统会自动推演出协议类型,除非遇到这样的情况:有两种不同的协议支持同一种地址类型和数据传输类型。

tcp_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); //IPPROTO_TCP表示TCP协议

这种套接字称为 TCP 套接字。

udp_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); //IPPROTO_UDP表示UDP协议

这种套接字称为 UDP 套接字。

int tcp_socket = socket(AF_INET, SOCK_STREAM, ); //创建TCP套接字
int udp_socket = socket(AF_INET, SOCK_DGRAM, ); //创建UDP套接字

bind() 函数

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

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

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[]; //不使用,一般用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。

in_addr 结构体

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

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

为什么使用 sockaddr_in 而不使用 sockaddr

bind() 第二个参数的类型为 sockaddr,而代码中却使用 sockaddr_in,然后再强制转换为 sockaddr,这是为什么呢?

sockaddr 结构体的定义如下:

struct sockaddr{
sa_family_t sin_family; //地址族(Address Family),也就是地址类型
char sa_data[]; //IP地址和端口号
};

sockaddr 和 sockaddr_in 的长度相同,都是16字节,只是将IP地址和端口号合并到一起,用一个成员 sa_data 表示。要想给 sa_data 赋值,必须同时指明IP地址和端口号,例如”127.0.0.1:80“,遗憾的是,没有相关函数将这个字符串转换成需要的形式,也就很难给 sockaddr 类型的变量赋值,所以使用 sockaddr_in 来代替。这两个结构体的长度相同,强制转换类型时不会丢失字节,也没有多余的字节。

connect() 函数

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

listen() 函数

int listen(SOCKET sock, int backlog); //Windows

sock 为需要进入监听状态的套接字,backlog 为请求队列的最大长度。

当套接字正在处理客户端请求时,如果有新的请求进来,套接字是没法处理的,只能把它放进缓冲区,待当前请求处理完毕后,再从缓冲区中读取出来处理。如果不断有新的请求进来,它们就按照先后顺序在缓冲区中排队,直到缓冲区满。这个缓冲区,就称为请求队列(Request Queue)。

缓冲区的长度(能存放多少个客户端请求)可以通过 listen() 函数的 backlog 参数指定,但究竟为多少并没有什么标准,可以根据需求来定,并发量小的话可以是10或者20。

如果将 backlog 的值设置为 SOMAXCONN,就由系统来决定请求队列长度,这个值一般比较大,可能是几百,或者更多。
当请求队列满时,就不再接收新的请求,对于 Linux,客户端会收到 ECONNREFUSED 错误,对于 Windows,客户端会收到 WSAECONNREFUSED 错误。

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

accept() 函数

SOCKET accept(SOCKET sock, struct sockaddr *addr, int *addrlen); //Windows

它的参数与 listen() 和 connect() 是相同的:sock 为服务器端套接字,addr 为 sockaddr_in 结构体变量,addrlen 为参数 addr 的长度,可由 sizeof() 求得。

accept() 返回一个新的套接字来和客户端通信,addr 保存了客户端的IP地址和端口号,而 sock 是服务器端的套接字。后面和客户端通信时,要使用这个新生成的套接字,而不是原来服务器端的套接字。accept() 会阻塞程序执行(后面代码不能被执行),直到有新的请求到来。

send() 函数

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

sock 为要发送数据的套接字,buf 为要发送的数据的缓冲区地址,len 为要发送的数据的字节数,flags 为发送数据时的选项。
返回值和前三个参数不再赘述,最后的 flags 参数一般设置为 0 或 NULL,初学者不必深究。

五.其它

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

#include <winsock2.h>  

使用DLL之前必须把DLL加载到当前程序,可以在编译时加载,也可以在程序运行时加载。这里使用#pragma命令,在编译时加载:

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

使用DLL之前,还需要调用 WSAStartup() 函数进行初始化,以指明 WinSock 规范的版本,它的原型为:

int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData);

wVersionRequested 为 WinSock 规范的版本号,低字节为主版本号,高字节为副版本号(修正版本号);lpWSAData 为指向 WSAData 结构体的指针。

关于 WinSock 规范:

WinSock 规范的最新版本号为 2.2

wVersionRequested 参数用来指明我们希望使用的版本号,它的类型为 WORD,等价于 unsigned short,是一个整数,所以需要用 MAKEWORD() 宏函数对版本号进行转换。例如:MAKEWORD(1, 2); //主版本号为1,副版本号为2,返回 0x0201

关于 WSAData 结构体

WSAStartup() 函数执行成功后,会将与 ws2_32.dll 有关的信息写入 WSAData 结构体变量。WSAData 的定义如下:

typedef struct WSAData {
WORD wVersion; //ws2_32.dll 建议我们使用的版本号
WORD wHighVersion; //ws2_32.dll 支持的最高版本号
char szDescription[WSADESCRIPTION_LEN+];//一个以 null 结尾的字符串,用来说明 ws2_32.dll 的实现以及厂商信息
char szSystemStatus[WSASYS_STATUS_LEN+];//一个以 null 结尾的字符串,用来说明 ws2_32.dll 的状态以及配置信息
unsigned short iMaxSockets; //2.0以后不再使用
unsigned short iMaxUdpDg; //2.0以后不再使用
char FAR *lpVendorInfo; //2.0以后不再使用
} WSADATA, *LPWSADATA;

最后3个成员已弃之不用,szDescription 和 szSystemStatus 包含的信息基本没有实用价值,读者只需关注前两个成员即可。

文件描述符有时也被称为文件句柄(File Handle),但“句柄”主要是 Windows 中术语。

windows C++ 网络编程的更多相关文章

  1. windows socket网络编程基础知识

    下面介绍网络7层协议在WINDOWS的实现: 7层协议 WIN系统 ________________________________________ 7 应用层 7 应用程序 ____________ ...

  2. windows socket 网络编程

    样例代码就在我的博客中,包含六个UDP和TCP发送接受的cpp文件,一个基于MFC的局域网聊天小工具project,和此小工具的全部执行时库.资源和执行程序.代码的压缩包位置是http://www.b ...

  3. windows socket网络编程资料汇集

    windows socket网络基础详解(socket的流程介绍的很详细)http://blog.csdn.net/ithzhang/article/details/8448655 Windows S ...

  4. windows socket网络编程--事件选择模型

    目录 事件选择模型概述 API详解 工作原理 代码实现 事件选择模型概述 Winsock提供了另一种有用的异步事件通知I/O模型--WSAEventSelect模型.这个模型与WSAAsyncSele ...

  5. Windows Socket网络编程-2016.01.07

    在使用WSAEventSelect的套接字模型中,遇到了WSAEventSelect返回10038的错误,在定位解决的过程中,简单记录一些定位解决的手段摘要. 使用windows的错误帮助信息,使用命 ...

  6. windows下网络编程UDP

    转载 C++ UDP客户端服务器Socket编程 UDPServer.cpp #include<winsock2.h>#include<stdio.h>#include< ...

  7. windows下网络编程TCP

    转载 sockets(套接字)编程有三种,流式套接字(SOCK_STREAM),数据报套接字(SOCK_DGRAM),原始套接字(SOCK_RAW): 基于TCP的socket编程 服务器端编程的步骤 ...

  8. Windows网络编程(C/C++服务器编程)

    Windows服务器网络编程 Linux服务器网络编程

  9. Windows Embedded Compact 7网络编程概述(上)

    如今,不论是嵌入式设备.PDA还是智能手机,网络都是必不可少的模块.网络使人们更方便地共享设备上的信息和资源.而且,利用智能手机浏览互联网,也逐渐成为生活中的常见手段.物联网所倡导的物物相联,也离不开 ...

随机推荐

  1. 【Python】time库

  2. make && make install

    ./configure  --prefix= /usr/local/python3.6.6 make &&  make  install  prefix=/usr/local/pyth ...

  3. OpenCV函数:提取轮廓相关函数使用方法

    opencv中提供findContours()函数来寻找图像中物体的轮廓,并结合drawContours()函数将找到的轮廓绘制出.首先看一下findContours(),opencv中提供了两种定义 ...

  4. 同步循环发请求用promise

    function ajax(image, ind) {     return new Promise(function(resolve, resject) {        setTimeout(fu ...

  5. String类中的equals方法总结(转载)

    转载:https://blog.csdn.net/qq_25827845/article/details/53868815 1.String源码中equals大致写法: public boolean ...

  6. php设计模式之工厂方法实例代码

    实现不修改原代码,扩展新功能 <?php header("Content-type:text/html;charset=utf-8"); /** * db接口 * 实现连接数 ...

  7. Sliding Window POJ - 2823 单调队列模板题

    Sliding Window POJ - 2823 单调队列模板题 题意 给出一个数列 并且给出一个数m 问每个连续的m中的最小\最大值是多少,并输出 思路 使用单调队列来写,拿最小值来举例 要求区间 ...

  8. angular6 路由拼接查询参数如 ?id=1 并获取url参数

    angular6 路由拼接查询参数如 ?id=1 并获取url参数 路由拼接参数: <div class="category-border" [routerLink]=&qu ...

  9. linux php 扩展安装

    phpize./configure --with-php-config=/usr/bin/php-config make && make install

  10. PAT-链表-A1032 Sharing

    题意:给出两条链表的首地址以及若干个节点的的地址.数据.下一个节点的地址,求两条链表的首个共用节点的地址.如果两条链表没有共用节点,则输出-1. 思路:使用静态链表,首先遍历一遍第一个链表并进行标记. ...