Socket通信

Socket通信

通信过程

服务端先初始化Socket,然后与端口进行绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。在这时如果有客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端服务端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束。

服务端
  1. 创建套接字
  2. 绑定套接字到一个IP地址和端口上
  3. 将套接字设置为监听模式等待连接请求
  4. 请求到来后,接受连接请求,返回一个新的对应于此次连接的新的套接字
  5. 用上一步返回的套接字与客户端进行通信
  6. 关闭套接字连接
客户端
  1. 创建套接字
  2. 向服务端发送请求的连接
  3. 与服务端进行通信
  4. 关闭套接字连接

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>

通信过程

服务端
  1. 加载套接字库并创建套接字
  2. 绑定套接字到一个IP地址和端口上
  3. 将套接字设置为监听模式等待连接请求
  4. 请求到来后,接受连接请求,返回一个新的对应于此次连接的新的套接字
  5. 用上一步返回的套接字与客户端进行通信
  6. 关闭套接字,关闭加载的套接字库
客户端
  1. 创建套接字
  2. 向服务端发送请求的连接
  3. 与服务端进行通信
  4. 关闭套接字,关闭加载的套接字库

加载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函数进行编码

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值