网络编程
实现两台或多台已经联网的计算机互相交换数据的行为,就是网络编程.
我们日常使用的操作系统已经为我们提供了socket, 不需要熟悉网络数据传输的原理,
也能掌握网络编程.
windows中的socket与Linux中的有何区别?
- 相同
Linux
通常会用文件描述符
来表示或区分已经打开了的文件;windows
通过文件句柄
的方式来表示,和上述的Linux
的文件描述符
是类似的概念;- 不同
Linux
的一切都是文件,所以网路连接也是一个文件.Windows
则会将socket
和文件区分开来,因此在Windows
中socket
有针对性设计的数据传输函数.
socket的类型
- SOCK_STREAM
面向连接的套接字(Stream Sockets)
,是对SOCK_STREAM
的说明.
使用了TCP
协议,有自己的纠错机制.
浏览器使用的http
协议就是基于面向连接的套接字
- SOCK_DGRAM
无连接的套接字(Datagram Sockets)
,也叫数据报格式套接字
.
它的传输效率相对于SOCK_STREAM
要高,但对数据的校验工作较少.
视频聊天和语音视频大多是采用无连接套接字
来传输数据的.
- SOCK_RAW
原始套接字(raw-protocol interface)
.
保存了数据包的完整IP
头,可以通过它来对数据进行分析.
网络安全产品通常使用此类型,如MAC
地址扫描器,网络嗅探器等产品.
TCP套接字
- 服务端
#include <iostream>
#include <WinSock2.h>
#pragma comment(lib,"ws2_32.lib")
using namespace std;
int main()
{
//初始化套接字库
WORD wVersion;
WSADATA wsaData;
int err;
wVersion = MAKEWORD(1, 1);
err = WSAStartup(wVersion, &wsaData);
//检查
if (err != 0)
{
return err;
}
if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1)
{
//清理套接字库
WSACleanup();
return -1;
}
//创建套接字
SOCKET sockSrv = socket(AF_INET, SOCK_STREAM, 0);
//准备绑定的信息
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
addrSrv.sin_family = AF_INET;
addrSrv.sin_port = htons(6000);
//绑定到本机
bind(sockSrv, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));
//监听
listen(sockSrv, 10);
cout << "server start at prot: 6000" << endl;
SOCKADDR addrCli;
int len = sizeof(SOCKADDR);
char recvBuf[100];
char sendBuf[100];
while (true)
{
//接受连接请求
SOCKET sockConn = accept(sockSrv, (SOCKADDR*)&addrCli, &len);
sprintf_s(sendBuf,100, "hello");
send(sockConn, sendBuf, strlen(sendBuf) + 1, 0);
//接受或发送数据
recv(sockConn, recvBuf, 100, 0);
std::cout << recvBuf << std::endl;
//关闭套接字
closesocket(sockConn);
}
//关闭套接字
closesocket(sockSrv);
//清理套接字库
WSACleanup();
system("pause");
return 0;
}
- 客户端
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <iostream>
#include <WinSock2.h>
#pragma comment(lib,"ws2_32.lib")
using namespace std;
int main()
{
//初始化套接字库
WORD wVersion;
WSADATA wsaData;
int err;
wVersion = MAKEWORD(1, 1);
err = WSAStartup(wVersion, &wsaData);
//检查
if (err != 0)
{
return err;
}
if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1)
{
//清理套接字库
WSACleanup();
return -1;
}
//创建tcp套接字
SOCKET sockCli = socket(AF_INET, SOCK_STREAM, 0);
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr = inet_addr("192.168.0.131");
//addrSrv.sin_addr.S_un.S_addr = htons(6000);//大失误
addrSrv.sin_port = htons(6000);
addrSrv.sin_family = AF_INET;
//连接服务器
connect(sockCli, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));
char sendBuf[] = "world";
char recvBuf[100];
//发送数据到服务器
send(sockCli, sendBuf, strlen(sendBuf) + 1, 0);
//接收服务器发送的数据
recv(sockCli, recvBuf, sizeof(recvBuf), 0);
cout << recvBuf << endl;
closesocket(sockCli);
WSACleanup();
system("pause");
return 0;
}
UDP套接字
- 服务端
#include <iostream>
#include <WinSock2.h>
#pragma comment(lib,"ws2_32.lib")
using namespace std;
int main()
{
//初始化套接字库
WORD wVersion;
WSADATA wsaData;
wVersion = MAKEWORD(1, 1);
int err = WSAStartup(wVersion, &wsaData);
//检查
if (err != 0)
{
cout << WSAGetLastError() << endl;
return err;
}
if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1)
{
cout << WSAGetLastError() << endl;
WSACleanup();
return -1;
}
//创建套接字
SOCKET sockSrv = socket(AF_INET, SOCK_DGRAM, 0);
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
addrSrv.sin_port = htons(6002);//主机字节序转换
addrSrv.sin_family = AF_INET;
//绑定到本机6002端口
bind(sockSrv, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));
//接受请求,处理请求
SOCKADDR_IN addrCli;
int len = sizeof(SOCKADDR);
char sendBuf[] = "UDP Server ...";
char recvBuf[100];
cout << "Start UDP Server with port 6002 " << endl;;
while (true)
{
//接收数据
recvfrom(sockSrv, recvBuf, 100, 0, (SOCKADDR*)&addrCli, &len);
cout << "Recv: " << recvBuf << endl;
//发送数据
sendto(sockSrv, sendBuf, strlen(sendBuf) + 1, 0, (SOCKADDR*)&addrCli, len);
cout << "Send: " << sendBuf << endl;
}
//关闭套接字并清除套接字库
closesocket(sockSrv);
WSACleanup();
system("pause");
return 0;
}
- 客户端
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <iostream>
#include <WinSock2.h>
#pragma comment(lib,"ws2_32.lib")
using namespace std;
int main()
{
//初始化套接字库
WORD wVersion;
WSADATA wsaData;
wVersion = MAKEWORD(1, 1);
int err = WSAStartup(wVersion, &wsaData);
//检查
if (err != 0)
{
return err;
}
if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1)
{
WSACleanup();
return -1;
}
//创建UDP套接字
SOCKET sockCli = socket(AF_INET, SOCK_DGRAM, 0);
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
addrSrv.sin_port = htons(6002);
addrSrv.sin_family = AF_INET;
SOCKADDR_IN addrCli;
int len = sizeof(SOCKADDR);
char sendBuf[] = "send from UDP client ...";
char recvBuf[100];
//发送数据到服务端并打印
cout << "send to server: " << sendBuf << endl;
sendto(sockCli, sendBuf, strlen(sendBuf) + 1, 0, (SOCKADDR*)&addrSrv, len);
//接收服务端数据并打印
recvfrom(sockCli, recvBuf, 100, 0, (SOCKADDR*)&addrCli, &len);
cout << "receve from: " << recvBuf << endl;
//关闭套接字并清除套接字库
closesocket(sockCli);
WSACleanup();
system("pause");
return 0;
}
常用函数分析
- WSAStartup()函数
是
Windows
操作系统特有的函数.
在Windows
系统中使用网络编程需要加载ws2_32.dll
动态链接库.
使用这个dll
之前需要调用WSAStartup()
函数初始化动态库.
函数原型:
int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData);
- socket()函数
程序使用
socket
函数来创建套接字,不管是Linux
还是Windows
都一样,区别是返回值不同.
Linux
中socket
函数的返回值是int
,Windows
里的返回值是SOCKET
.
我这里练习的环境是windows
,所以这里的代码例子都以Windows
为准.
函数原型:
SOCKET socket(int af, int type, int protocol);
- bind()函数
创建完套接字之后需要确定套接字的各种属性,比如
IP
地址,端口等信息.
这些信息用一个sockaddr
结构体变量存放.
函数原型:
int bind(SOCKET sock, const struct sockaddr *addr, int addrlen);
- connect()函数
是客户端在创建完套接字后,用来建立连接的.
它的参数和bind
相同.
函数原型:
int connect(SOCKET sock, const struct sockaddr *serv_addr, int addrlen);
- listen()函数
在服务端绑定完套接字之后,还使用
listen
函数让套接字进入被动监听状态.
第二个参数是请求队列的最大长度.
函数原型:
int listen(SOCKET sock, int backlog);
- accept()函数
套接字通过
listen
函数进入被动监听状态之后,通过accept
函数接收客户端请求.
它的参数跟listen
和connect
函数相同.
函数原型:
SOCKET accept(SOCKET sock, struct sockaddr *addr, int *addrlen);
- send()函数
经过上面一系列的连接的目的,自然是为了实现服务端与客户端之间的发送和接收数据.
send
函数是从服务端发送数据,第四个参数可以参考send recv函数中的flags参数
函数原型:
int send(SOCKET sock, const char *buf, int len, int flags);
- recv()函数
recv
函数的功能是从服务端或客户端接收数据.
它和send
函数的参数是一样的.
函数原型:
int recv(SOCKET sock, char *buf, int len, int flags);
总结
这篇博客内容只是简单的涵盖了socket
编程需要用到的函数,
以及给出了简单的示例代码.对于socket
编程入门还是有帮助的.\
Windows
操作系统拓展的套接字函数一般以WSA
开头,它的意思是Windows socket API
.常用的比如:
WSASend
,WSARecv
以及前面提到的WSACleanup
等.