记录下学习网络通信编程的知识。
C++网络编程
一.概念理解
1.Socket通信模型
网络通信就好比去邮局寄信这个过程。Socket就相当于寄信这个过程中,邮局门口的邮筒,所以Socket也称套接字。
2.Socket通信流程
3.关于IP地址
①查询自己电脑的IP地址
②nslookup
nslookup的作用:将www.baidu.com这个域名解析为IP地址
即也可以通过输入http://180.101.49.11(180.101.49.11)也能访问到百度网页。
二.Socket通信函数
WSAStartup()
int WSAStartup(WORD wVersionRequested,LPWSADATA lpWSAData)
使用Socket之前必须调用此函数
第一个参数:Socket版本
第二个参数:操作系统通过它返回请求Socket版本信息
WSACleanup()
int WSACleanup(void)
应用程序在完成对请求的Socket库使用之后,要调用此函数来解除与Socket库的绑定并且释放Socket库所占用的系统资源。
socket()
SOCKET socket(int af,int type,int protocol)
应用程序调用socket函数来创建网络通信的套接字。
参数介绍:
-af:一个地址描述。目前仅支持AF_INET格式,也就是ARPA Internet地址格式。
-type:指定socket类型。新套接口的类型描述类型,如TCP(SOCK_STREAM)和UDP(SOCK_DGRAM)。
-protocol:指定协议,套接口所用的协议。如调用者不想指定,可用0。常用的协议有IPPROTO_TCP、IPPROTO_UDP等。
closesocket()
int closesocket(SOCKET s)
关闭一个描述符为s的套接字,执行成功返回0,否则返回SOCKET_ERROR。
send()
int send(SOCKET s,const char FAR* buf,int len,int flags)
不管是客户端还是服务端应用程序,都用此函数向TCP连接的另一端发送数据。
向一个已连接的套接口发送数据。
-s:一个用于标识已连接套接口的描述字,是目的地的套接口。
-buf:包含待发送数据的缓冲区。
-len:缓冲区中数据的长度。
-flags:调用执行方式。
第一个:SOCKET的描述符
第二个:存放应用程序要发送数据的缓冲区
第三个:指明实际要发送的数据的字节数
第四个:一般设置为0
recv()
int recv(SOCKET s,char FAR *buf,int len,int flags)
第三个参数:指明buf的长度。
第四个参数:一般设置为0。
bind()
int bind(SOCKET s,const struct sockadrr FAR* name,int namelen)
创建一个socket之后,套接字数据结构中有一个默认的IP地址和默认端口,一个服务程序必须调用bind给其绑定一个IP和端口,客户端程序不需要绑定。
第一个:指定绑定的Socket描述符
第二个:指定Socket结构
第三个:指定名称长度
如无错误发生,则bind()返回0,否则的话,将返回-1,应用程序可通过WSAGetLastError()获取相应的错误代码。
listen()
int listen(SOCKET s,int backlog)
服务程序可以调用listen函数使流式套接字s处于监听状态。执行成功返回0,否则返回SOCKET_ERROR。
-s:用于标识一个已捆绑未连接套接口的描述字。
-backlog:等待连接队列的最大长度。
功能:监听通过socket发起的连接请求,将其加入等待队列,等待accept函数的接收。
accept()
SOCKET accept(SOCKET s,struct sockaddr FAR* addr,int FAR* addrlen)
服务端程序调用,从处于监听状态的流式套接字s的客户连接请求队列中取出排在最前面的客户请求。
本函数从s的等待连接队列中抽取第一个连接,创建一个与s同类的新的套接口并返回句柄。
-s:套接字描述符,该套接口在listen()后监听连接。
-addr:(可选)指针,指向一缓冲区,其中接收为通讯层所知的连接实体的地址。Addr参数的实际格式由套接口创建时所产生的地址族确定。
-addrlen:(可选)指针,输入参数,配合addr一起使用,指向存有addr地址长度的整型数。
connect()
int connect(SOCKET s,const struct sockaddr FAR* name,int namelen)
-s:标识一个未连接socket
-name:指向要连接套接字的sockaddr结构体的指针
-namelen:sockaddr结构体的字节长度
客户程序调用,使客户的socket s与监听于name所指定的计算机特定端口上的服务socket进行连接,连接成功则返回0,否则返回SOCKET_ERROR。
本函数用于创建与指定外部端口的连接。s参数指定一个未连接的数据报或流类套接口。
如套接口未被捆绑,则系统赋给本地关联一个唯一的值,且设置套接口为已捆绑。请注意若名字结构中的地址域为全零的话,则connect()将返回WSAEADDRNOTAVAIL错误。
对于流类套接口(SOCK_STREAM类型),利用名字来与一个远程主机建立连接,一旦套接口调用成功返回,它就能收发数据了。对于数据报类套接口(SOCK_DGRAM类型),则设置成一个缺省的目的地址,并用它来进行后续的send()与recv()调用。
ioctlsocket()
int ioctlsocket(SOCKET s,long cmd,u_long FAR *argp)
ioctlsocket()是一个计算机函数,功能是控制套接口的模式。可用于任一状态的任一套接口。它用于获取与套接口相关的操作参数,而与具体协议或通讯子系统无关。
-s:一个标识套接口的描述字。
*cmd:对套接口s的操作命令。
-argp:指向cmd命令所带参数的指针。
三.IP处理函数
inet_aton()
int inet_aton(const char *string, struct in_addr*addr)
参数描述:
1 输入参数string包含ASCII表示的IP地址。
2 输出参数addr是将要用新的IP地址更新的结构。
返回值:
如果这个函数成功,函数的返回值非零,如果输入地址不正确则会返回零。使用这个函数并没有错误码存放在errno中,所以它的值会被忽略。
inet_addr()
in_addr_t inet_addr(const char *cp)
inet_addr函数转换“网络主机地址”(如192.168.1.10)为“网络字节序二进制值”,如果参数char *cp无效,
函数返回-1(INADDR_NONE),这个函数在处理地址为255.255.255.255时也返回-1,255.255.255.255是一个有效的地址,不过inet_addr无法处理。
inet_nota()
char *inet_ntoa(struct in_addr in)
inet_nota函数转换“网络字节排序的地址”为“标准的ASCII以点分开的地址”,该函数返回指向点分开的字符串地址(如192.168.1.10)的指针,
该字符串的空间为静态分配的,这意味着在第二次调用该函数时,上一次调用将会被覆盖,所以如果需要保存该字符串需要最后复制出来自己管理。
四.小案例:一个服务端和一个客户端通信
①在vs中建立两个项目,一个是服务端,一个是客户端
②将两个项目的属性设置如下
服务端.cpp
#include <iostream>
#include <stdlib.h>
#include <stdio.h>
#include <WinSock2.h> //Windows Socket编程头文件
#include <cstring>
#pragma comment(lib,"ws2_32.lib") //链接ws2_32.lib库文件到此项目中
using namespace std;
//================================全局常量================================
const int BUF_SIZE = 2048; //缓冲区大小
//================================全局变量================================
SOCKET sockSer,sockCli; //服务端、客户端套接口
SOCKADDR_IN addrSer, addrCli; //服务端、客户端的地址
int naddr = sizeof(SOCKADDR_IN);
char sendbuf[BUF_SIZE]; // 发送缓冲区
char inputbuf[BUF_SIZE]; //输入缓冲区
char recvbuf[BUF_SIZE]; //接受缓冲区
int main()
{
//载入socket库
WSADATA wsadata;
if (WSAStartup(MAKEWORD(2, 2), &wsadata) != 0)
{
//输出出错信息
cout << "载入socket库失败" << endl;
system("pause");
return 0;
}
//创建socket
sockSer = socket(AF_INET, SOCK_STREAM, 0);
//初始化服务器地址相关信息
addrSer.sin_addr.s_addr = inet_addr("192.168.101.127"); //填自己电脑的IP地址
addrSer.sin_family = AF_INET;
addrSer.sin_port = htons(12248); //端口号随便写,最大65535
//绑定socket和本地地址
bind(sockSer, (SOCKADDR*)&addrSer, sizeof(SOCKADDR));
while (1) //不断的监听是否有连接请求
{
//监听socket
listen(sockSer, 5);
//接受连接请求
sockCli = accept(sockSer, (SOCKADDR*)&addrCli, &naddr);
if (sockCli != INVALID_SOCKET) //连接成功
{
cout << "连接成功!" << endl;
strcpy(sendbuf, "欢迎!");
send(sockCli, sendbuf, sizeof(sendbuf), 0);
}
}
closesocket(sockSer);
closesocket(sockCli);
WSACleanup();
return 0;
}
客户端.cpp
#include <iostream>
#include <stdlib.h>
#include <stdio.h>
#include <WinSock2.h> //Windows Socket编程头文件
#include <cstring>
#pragma comment(lib,"ws2_32.lib") //链接ws2_32.lib库文件到此项目中
using namespace std;
//================================全局常量================================
const int BUF_SIZE = 2048; //缓冲区大小
//================================全局变量================================
SOCKET sockSer, sockCli; //服务端、客户端套接口
SOCKADDR_IN addrSer, addrCli; //服务端、客户端的地址
int naddr = sizeof(SOCKADDR_IN);
char sendbuf[BUF_SIZE]; // 发送缓冲区
char inputbuf[BUF_SIZE]; //输入缓冲区
char recvbuf[BUF_SIZE]; //接受缓冲区
int main()
{
//载入socket库
WSADATA wsadata;
if (WSAStartup(MAKEWORD(2, 2), &wsadata) != 0)
{
//输出出错信息
cout << "载入socket库失败" << endl;
system("pause");
return 0;
}
//创建socket
sockCli = socket(AF_INET, SOCK_STREAM, 0);
//初始化客户端地址相关信息
addrCli.sin_addr.s_addr = inet_addr("127.0.0.1"); //这个IP地址是用来测试的
addrCli.sin_family = AF_INET;
addrCli.sin_port = htons(12248);
//初始化服务器地址相关信息
addrSer.sin_addr.s_addr = inet_addr("192.168.101.127"); //填自己电脑的IP地址
addrSer.sin_family = AF_INET;
addrSer.sin_port = htons(12248);
while (1)
{
if (connect(sockCli, (SOCKADDR*)&addrSer, sizeof(addrSer)) != SOCKET_ERROR)
{
recv(sockCli, recvbuf, sizeof(recvbuf), 0);
cout << recvbuf << endl;
}
}
closesocket(sockSer);
closesocket(sockCli);
WSACleanup();
return 0;
}
效果: