Socket通信
通信过程
服务端先初始化Socket
,然后与端口进行绑定
(bind),对端口进行监听
(listen),调用accept阻塞
,等待客户端连接。在这时如果有客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端
与服务端
的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接
,一次交互结束。
服务端
- 创建套接字
- 绑定套接字到一个IP地址和端口上
- 将套接字设置为监听模式等待连接请求
- 请求到来后,接受连接请求,返回一个新的对应于此次连接的新的套接字
- 用上一步返回的套接字与客户端进行通信
- 关闭套接字连接
客户端
- 创建套接字
- 向服务端发送请求的连接
- 与服务端进行通信
- 关闭套接字连接
Python服务端
创建
socket.socket(family=AF_INET, type=SOCK_STREAM, proto=0)
使用给定的地址族、套接字类型、协议号(通常为零并且可以省略)来创建socket
参数介绍
地址族 | 简述 |
---|---|
socket.AF_UNIX | 只能够用于单一的Unix系统进程间通信 |
socket.AF_INET(默认值) | 服务器之间网络通信 |
套接字类型 | 简述 |
---|---|
socket.SOCK_STREAM(默认值) | 流式socket, TCP |
socket.SOCK_DGRAM | 数据报式socket, UDP |
为什么要选择TCP协议而不是UDP协议?
- TCP:传输相对可靠,保证数据正确性和顺序,速度相对慢
- UDP:传输相对不可靠,用于传输少量数据,速度相对快
绑定
socket.bind(address)
将套接字绑定到address,套接字必须尚未绑定,在AF_INET
下,以元组(host,port)
的形式表示地址
监听
socket.listen([backlog])
开始监听TCP传入连接。backlog指定在拒绝连接之前,操作系统可以挂起的最大连接数量。该值至少为1
接收客户端连接
socket.accept()
接受一个客户端的连接。此 socket 必须绑定(bind)
到一个地址上并且监听(listen)
连接。返回值是一个(conn, address) 对
,其中 conn 是一个 新 的套接字对象,用于在此连接上收发数据
,address 是连接该服务端的客户端的套接字所绑定的地址,socket.getpeername()
函数也能返回该地址。
在服务器端,socket()返回的套接字用于监听(listen)和接受(accept),这个套接字不能用于与客户端之间发送和接收数据。
accept()接受一个客户端的连接请求,并返回一个新的套接字,不同于以上socket()返回的用于监听和接受客户端的连接请求的套接字,与此客户端通信是通过这个新的套接字上发送和接收数据来完成的。
发送消息
s.send(string[,flag])
发送TCP数据。将string中的数据发送到连接的套接字。返回值是要发送的字节数量,该数量可能小于string的字节大小。
s.sendall(string[,flag])
完整发送TCP数据。将string中的数据发送到连接的套接字,但在返回之前会尝试发送所有数据。成功返回None,失败则抛出异常。
接收消息
socket.recv(bufsize[, flags])
从套接字接收数据。返回值是一个字节对象,表示接收到的数据。bufsize 指定一次接收的最大数据量。可选参数 flags 默认为零。
为了最佳匹配硬件和网络的实际情况,bufsize 的值应为 2 的相对较小的幂,如 4096。
Python客户端
请求连接服务端
socket.connect(address)
连接到address处的套接字。一般address的格式为元组(hostname,port)。
发送消息
s.send(string[,flag])
发送TCP数据。将string中的数据发送到连接的套接字。返回值是要发送的字节数量,该数量可能小于string的字节大小。
s.sendall(string[,flag])
完整发送TCP数据。将string中的数据发送到连接的套接字,但在返回之前会尝试发送所有数据。成功返回None,失败则抛出异常。
接收消息
socket.recv(bufsize[, flags])
从套接字接收数据。返回值是一个字节对象,表示接收到的数据。bufsize 指定一次接收的最大数据量。可选参数 flags 默认为零。
为了最佳匹配硬件和网络的实际情况,bufsize 的值应为 2 的相对较小的幂,如 4096。
Socket公共函数
关闭套接字连接
socket.close()
返回远程地址
socket.getpeername()
返回连接套接字的远程地址。返回值通常是元组(ipaddr,port)
返回自己地址
socket.getsockname()
返回套接字自己的地址。返回值通常是元组(ipaddr,port)
服务端
import socket
import os
import time
if __name__ == '__main__':
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #创建套接字
server.bind(("192.168.1.10", 8889)) #绑定IP和端口
server.listen(0) #设置监听
print("333")
connection, address = server.accept()
print("客户端连接成功")
while True:
#print("----")
#print(connection, address)
#print(connection.getpeername())
recv_str=connection.recv(1024) #1024是最大长度
#print(recv_str)
print(recv_str.decode("gbk"))
recv_str=recv_str.decode("gbk")
print(recv_str,type(recv_str),type("退出程序"),recv_str=="退出程序")
if recv_str=="退出程序":
print("jcd")
break
connection.send( bytes("服务器接收到的数据:%s" % recv_str,encoding="gbk") )
#print("0000")
#print("ppppppppppp")
connection.close()
print("已退出")
Java
C++
头文件
#include<winsock.h>
通信过程
服务端
- 加载套接字库并创建套接字
- 绑定套接字到一个IP地址和端口上
- 将套接字设置为监听模式等待连接请求
- 请求到来后,接受连接请求,返回一个新的对应于此次连接的新的套接字
- 用上一步返回的套接字与客户端进行通信
- 关闭套接字,关闭加载的套接字库
客户端
- 创建套接字
- 向服务端发送请求的连接
- 与服务端进行通信
- 关闭套接字,关闭加载的套接字库
加载Winsock库
WSADATA wsa;
/*初始化socket资源*/
if (WSAStartup(MAKEWORD(1,1),&wsa) != 0)
{
return; //代表失败
}
释放Winsock库
WSACleanup();
创建套接字
服务端
构造监听Socket,流式Socket
SOCKET Listen_Sock = socket(AF_INET, SOCK_STREAM, 0)
客户端
构造通讯Socket,流式Socket
SOCKET Client_Sock = socket(AF_INET, SOCK_STREAM, 0)
服务端配置监听地址和端口
SOCKADDR_IN serverAddr
ZeroMemory((char *)&serverAddr,sizeof(serverAddr));
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(1234); /*本地监听端口:1234*/
serverAddr.sin_addr.s_addr = htonl(INADDR_ANY); /*有IP*/
sin_port和sin_addr都必须是网络字节序(NBO),一般可视化的数字都是主机字节序(HBO)。htons将无符号短整型主机字节序转换为网络字节序
第四式: 绑定SOCKET:
1.服务端:绑定监听SOCKET.
bind(Listen_Sock,(struct sockaddr *)&serverAddr,sizeof(serverAddr))
第五式: 服务端/客户端连接:
1.服务端:等待客户端接入.
SOCKET Command_Sock = accept(Listen_Sock, …)
2.客户端:请求与服务端连接.
int ret = connect(Client_Sock, …)
第六式: 收/发数据:
1.服务端:等待客户端接入.char buf[1024].
接收数据:recv(Command_Sock,buf, …)
或
发送数据:send(Command_Sock,buf, …)
2.客户端:请求与服务端连接.char buf[1024].
发送数据:send(Client_Sock,buf, …)
或
接收数据:recv(Client_Sock,buf, …)
第七式: 关闭SOCKET:
1.服务端:关闭SOCKET.
closesocket(Listen_Sock)
closesocket(Command_Sock)
2.客户端:关闭SOCKET.
closesocket(Client_Sock)
服务端
#include "pch.h"
#include<iostream>
#include<winsock.h>
#pragma comment(lib,"ws2_32.lib")
using namespace std;
void initialization();
int main() {
//定义长度变量
int send_len = 0;
int recv_len = 0;
int len = 0;
//定义发送缓冲区和接受缓冲区
char send_buf[100];
char recv_buf[100];
//定义服务端套接字,接受请求套接字
SOCKET s_server;
SOCKET s_accept;
//服务端地址客户端地址
SOCKADDR_IN server_addr;
SOCKADDR_IN accept_addr;
initialization();
//填充服务端信息
server_addr.sin_family = AF_INET;
server_addr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
//这里的IUNADDR_ANY 表示 监听本机的所有IP
server_addr.sin_port = htons(5010);
//创建套接字
s_server = socket(AF_INET, SOCK_STREAM, 0);
if (bind(s_server, (SOCKADDR *)&server_addr, sizeof(SOCKADDR)) == SOCKET_ERROR) {
cout << "套接字绑定失败!" << endl;
WSACleanup();
}
else {
cout << "套接字绑定成功!" << endl;
}
//设置套接字为监听状态
if (listen(s_server, SOMAXCONN) < 0) {
cout << "设置监听状态失败!" << endl;
WSACleanup();
}
else {
cout << "设置监听状态成功!" << endl;
}
cout << "服务端正在监听连接,请稍候...." << endl;
//接受连接请求
len = sizeof(SOCKADDR);
s_accept = accept(s_server, (SOCKADDR *)&accept_addr, &len);
if (s_accept == SOCKET_ERROR) {
cout << "连接失败!" << endl;
WSACleanup();
return 0;
}
cout << "连接建立,准备接受数据" << endl;
//接收数据
while (1) {
recv_len = recv(s_accept, recv_buf, 100, 0);
if (recv_len < 0) {
cout << "接受失败!" << endl;
break;
}
else {
cout << "客户端信息:" << recv_buf << endl;
}
cout << "请输入回复信息:";
cin >> send_buf;
send_len = send(s_accept, send_buf, 100, 0);
if (send_len < 0) {
cout << "发送失败!" << endl;
break;
}
}
//关闭套接字
closesocket(s_server);
closesocket(s_accept);
//释放DLL资源
WSACleanup();
return 0;
}
void initialization() {
//初始化套接字库
WORD w_req = MAKEWORD(2, 2);//版本号
WSADATA wsadata;
int err;
err = WSAStartup(w_req, &wsadata);
if (err != 0) {
cout << "初始化套接字库失败!" << endl;
}
else {
cout << "初始化套接字库成功!" << endl;
}
//检测版本号
if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wHighVersion) != 2) {
cout << "套接字库版本号不符!" << endl;
WSACleanup();
}
else {
cout << "套接字库版本正确!" << endl;
}
//填充服务端地址信息
}
客户端
#include "pch.h"
#include<iostream>
#include<winsock.h>
#pragma comment(lib,"ws2_32.lib")
using namespace std;
void initialization();
int main() {
//定义长度变量
int send_len = 0;
int recv_len = 0;
//定义发送缓冲区和接受缓冲区
char send_buf[100];
char recv_buf[100];
//定义服务端套接字,接受请求套接字
SOCKET s_server;
//服务端地址客户端地址
SOCKADDR_IN server_addr;
initialization();
//填充服务端信息
server_addr.sin_family = AF_INET;
server_addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
server_addr.sin_port = htons(1234);
//创建套接字
s_server = socket(AF_INET, SOCK_STREAM, 0);
if (connect(s_server, (SOCKADDR *)&server_addr, sizeof(SOCKADDR)) == SOCKET_ERROR) {
cout << "服务器连接失败!" << endl;
WSACleanup();
}
else {
cout << "服务器连接成功!" << endl;
}
//发送,接收数据
while (1) {
cout << "请输入发送信息:";
cin >> send_buf;
send_len = send(s_server, send_buf, 100, 0);
if (send_len < 0) {
cout << "发送失败!" << endl;
break;
}
recv_len = recv(s_server, recv_buf, 100, 0);
if (recv_len < 0) {
cout << "接受失败!" << endl;
break;
}
else {
cout << "服务端信息:" << recv_buf << endl;
}
}
//关闭套接字
closesocket(s_server);
//释放DLL资源
WSACleanup();
return 0;
}
void initialization() {
//初始化套接字库
WORD w_req = MAKEWORD(2, 2);//版本号
WSADATA wsadata;
int err;
err = WSAStartup(w_req, &wsadata);
if (err != 0) {
cout << "初始化套接字库失败!" << endl;
}
else {
cout << "初始化套接字库成功!" << endl;
}
//检测版本号
if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wHighVersion) != 2) {
cout << "套接字库版本号不符!" << endl;
WSACleanup();
}
else {
cout << "套接字库版本正确!" << endl;
}
//填充服务端地址信息
}
通常来讲unicode占用2个字节。
那么utf-8又是什么呢?utf-8的产生是为了节省空间而出现的,前面讲到unicode通常占用2个字节,但是对于英文字符其实只需要一个字节就可以表示了,因此utf-8为了节省空间实现了动态长度的表示,比如它在编码中对英文字符用1个字节表示,而通常对于汉字就是用3个字节表示。
\u则代表unicode编码,是一个字符;
0x开头代表十六进制,实际上就是一个整数;
\x对应的是UTF-8编码的数据,通过转化规则可以转换为Unicode编码,就能得到对应的汉字,转换规则很简单,先将\x去掉,转换为数字
python3中默认对字符串采用的是unicode编码的str类型来表示,任一字符用两字节表示
unicode编码对所有语言使用两个字节,部分汉语使用三个字节
但是这就导致一个问题,就是unicode不仅不兼容ascii编码,而且会造成空间的浪费,于是uft-8编码应运而生了,utf-8编码对英文使用一个字节的编码
在python2中字符串分为 unicode 和 str 类型
在python3中字符串分为 str 和 bytes 两种类型
Str To Bytes 使用 encode(), 编码
Bytes To Str 使用 decode(), 解码
python3默认字符编码为:utf-8, python2默认字符编码为:ASCII
1.Python默认字符和文件编码
(1).python3.x 默认的字符编码是Unicode,默认的文件编码是utf-8
(2).python2.x 默认的字符编码是ASCII,默认的文件编码是ASCII
2.Python字符串编码和解码
(1)默认字符串是Unicode类型,该类型字符串只能保存在内存中
(2)bytes类型字符串,可以保存在磁盘和网络间数据传输
(3)字符串从Unicode到bytes,需要编码:str.enconde(“utf-8”)
(4)字符串从bytes到Unicode,需要解码:str.decode(“utf-8”)
https://www.zhihu.com/question/60231684
Python3中的str的默认编码格式是unicode Unicode默认占用两个字节的长度,为了避免浪费,我们使用utf-8的可变长度的编码格式来解决浪费问题,ASCII占用一个字节,只能表示英文,
str(Unicode)->bytes 我们要使用encode函数进行编码
bytes -> str(Unicode) 我们要使用decode函数进行编码