首先介绍,python的socket,就是编程接口(API),对TCP/IP的封装
UDP by python
服务器端:设置好socket后就等着客户端的请求,收到请求后发消息
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)#设置为ipv4,UDO
s.bind(('127.0.0.1', 6666))# 绑定 客户地址&端口:
print ('Build UDP on 6666 port')
while True:
# 接收数据 自动阻塞 等待客户端请求:
data, addr = s.recvfrom(1024)
print ('Received from %s:%s.' % addr)
s.sendto('Hello, %s!' % data, addr)
#recvfrom()方法返回数据和客户端的地址与端口,这样,服务器收到数据后,直接调用sendto()就可以把数据用UDP发给客户端。
客户端:设置好socket后,发送data,再print接收到的data
#encoding=utf8
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
for data in ['NEUQ', 'Li Haoyuan']:
# 发送数据:
s.sendto(data, ('127.0.0.1', 6666))
# 接收数据:
print (s.recv(1024))
s.close()
结果:
TCP
在UDP基础上需要三次握手保证可靠传输,多了listen,connect等操作
服务器端:首先调用socket函数获得文件描述符并配置socket参数,如端口、协议类型,再为套接字分配本地IP和协议端口。准备完成后进入阻塞状态,通过listen函数等待来自客户端的连接请求。当连接队列中有连接时,返回一个连接并输出连接者IP和端口号。调用send函数给连接者发送一条消息,之后关闭连接和监听。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define PORT 1234
#define BACKLOG 1
int main()
{
int listenfd, connectfd;
struct sockaddr_in server;
struct sockaddr_in client;
socklen_t addrlen;
// socket函数 调用socket获得文件描述符
// 参数:IPV_4协议 接口类型 是否为原始套接口
if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
perror("Socket error\n");
exit(1);
}
int opt = SO_REUSEADDR;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
bzero(&server, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(PORT);
server.sin_addr.s_addr = htonl(INADDR_ANY);
// bind函数 为套接口分配本地IP和协议端口
// 参数:socket返回的描述字 指向协议地址结构的指针 地址结构长度
if (bind(listenfd, (struct sockaddr *)&server, sizeof(server)) == -1) {
perror("bind error!\n");
exit(1);
}
// listen函数 等待来自客户端的连接请求
// 参数:socket 最大连接个数(定为1)
if (listen(listenfd, BACKLOG) == -1) {
perror("listen error!\n");
exit(1);
}
// 从已连接队列中返回一个连接,如果队列为空则睡眠等待
addrlen = sizeof(client);
if ((connectfd = accept(listenfd, (struct sockaddr*)&client, &addrlen)) == -1) {
perror("connect queue error\n");
exit(1);
}
// 服务器端显示信息
printf("A connection from client's ip is %s, port is %d\n", inet_ntoa(client.sin_addr), htons(client.sin_port));
// 连接方接收信息
// send函数用于数据发送
send(connectfd, "Hello LHY!\n", 22, 0);
close(connectfd);
close(listenfd);
return 0;
}
客户端: 运行时需要指定参数:IP地址,检查完参数数量后,申请socket,并调用connect函数请求和服务器的连接,通过三次握手进行连接。连接成功后,调用recv函数接收服务器端数据,处理数据(在末尾增加终止符)后输出到本地,之后断开连接。
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<netdb.h>
#define PORT 1234
#define MAXDATASIZE 100
int main(int argc, char *argv[])
{
int sockfd, num;
char buf[MAXDATASIZE];
struct hostent *he;
struct sockaddr_in server;
// 读取主机名及套接字参数
if (argc != 2) {
printf("Usage:%s <IP Address>\n", argv[0]);
exit(1);
}
if ((he = gethostbyname(argv[1])) == NULL) {
printf("Can not get host!\n");
exit(1);
}
// 申请socket
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
printf("Socket error!\n");
exit(1);
}
bzero(&server, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(PORT);
server.sin_addr = *((struct in_addr *)he->h_addr);
// 建立连接 TCP三次握手
if (connect(sockfd, (struct sockaddr *)&server, sizeof(server)) == -1) {
printf("TCP connect Error!\n");
exit(1);
}
// 接收服务器数据
if ((num = recv(sockfd, buf, MAXDATASIZE, 0)) == -1) {
printf("recv data error!\n");
exit(1);
}
buf[num - 1] = '\0';
printf("Server return message: %s\n", buf);
close(sockfd);
return 0;
}
结果:
ping的实现
参考。首先计算checksum校验和,和前面一样,通过socket套接字,再利用struct字节打包成二进制,最后用select返回套接字的文件描述符的结合,实现Ping程序。
#-*-coding: UTF-8 -*-
import time
import struct
import socket
import select
import sys
def chesksum(data):
"""
校验
"""
n = len(data)
m = n % 2
sum = 0
for i in range(0, n - m ,2):
sum += (data[i]) + ((data[i+1]) << 8)#传入data以每两个字节(十六进制)通过ord转十进制,第一字节在低位,第二个字节在高位
if m:
sum += (data[-1])
#将高于16位与低16位相加
sum = (sum >> 16) + (sum & 0xffff)
sum += (sum >> 16) #如果还有高于16位,将继续与低16位相加
answer = ~sum & 0xffff
#主机字节序转网络字节序列(参考小端序转大端序)
answer = answer >> 8 | (answer << 8 & 0xff00)
return answer
#连接套接字,并将数据发送到套接字
def raw_socket(dst_addr,imcp_packet):
rawsocket = socket.socket(socket.AF_INET,socket.SOCK_RAW,socket.getprotobyname("icmp"))
send_request_ping_time = time.time()
#send data to the socket
rawsocket.sendto(imcp_packet,(dst_addr,80))#imcp_packet是整个要发的包
return send_request_ping_time,rawsocket,dst_addr
#request ping
def request_ping(data_type,data_code,data_checksum,data_ID,data_Sequence,payload_body):
#把字节打包成二进制数据
imcp_packet = struct.pack('>BBHHH32s',data_type,data_code,data_checksum,data_ID,data_Sequence,payload_body)
icmp_chesksum = chesksum(imcp_packet)#获取校验和
icmp_packet = struct.pack('>BBHHH32s',data_type,data_code,icmp_chesksum,data_ID,data_Sequence,payload_body)
return icmp_packet
#reply ping
def reply_ping(send_request_ping_time,rawsocket,data_Sequence,timeout = 2):
while True:
started_select = time.time()
what_ready = select.select([rawsocket], [], [], timeout)
wait_for_time = (time.time() - started_select)
if what_ready[0] == []: # Timeout
return -1
time_received = time.time()
received_packet, addr = rawsocket.recvfrom(1024)
icmpHeader = received_packet[20:28]
type, code, checksum, packet_id, sequence = struct.unpack(
">BBHHH", icmpHeader
)
if type == 0 and sequence == data_Sequence:
return time_received - send_request_ping_time
timeout = timeout - wait_for_time
if timeout <= 0:
return -1
#实现 ping 主机/ip
def ping(host):
data_type = 8 # ICMP Echo Request
data_code = 0 # must be zero
data_checksum = 0 # "...with value 0 substituted for this field..."
data_ID = 0 #Identifier
data_Sequence = 1 #Sequence number
payload_body = b'NEUQ' #data
dst_addr = socket.gethostbyname(host)#将主机名转ipv4地址格式,返回以ipv4地址格式的字符串,如果主机名称是ipv4地址,则它将保持不变
print("正在 Ping {0} [{1}] 具有 32 字节的数据:".format(host,dst_addr))
for i in range(0,4):
icmp_packet = request_ping(data_type,data_code,data_checksum,data_ID,data_Sequence + i,payload_body)
send_request_ping_time,rawsocket,addr = raw_socket(dst_addr,icmp_packet)
times = reply_ping(send_request_ping_time,rawsocket,data_Sequence + i)
if times > 0:
print("来自 {0} 的回复: 字节=32 时间={1}ms".format(addr,int(times*1000)))
time.sleep(0.7)
else:
print("请求超时。")
if __name__ == "__main__":
# if len(sys.argv) < 2:
# sys.exit('Usage: ping.py <host>')
# ping(sys.argv[1])
ping("www.baidu.com")
结果: