Socket 编程实战

文章目录

  1. Socket库
    0.1. 什么是 Socket?
    0.2. socket()函数
    0.3. Socket 对象(内建)方法
    0.4. 简单实例
    0.5. Python Internet 模块
  2. 概述
  3. Python socket API
    2.1. TCP socket
    2.2. UDP socket
  4. 常见陷阱
    3.1. 忽略返回值
    3.2. 误认为 TCP 具有 framing
  5. TCP 的状态机
  6. 实战
    5.1. HTTP UA
    5.2. Unix_domain_socket
    5.3. ping
    5.4. netstat vs ss
  7. 总结
  8. SocketTool 编程调试工具

Socket 在英文中的含义为“(连接两个物品的)凹槽”,像the eye socket,意为“眼窝”,此外还有“插座”的意思。在计算机科学中,socket 通常是指一个连接的两个端点,这里的连接可以是同一机器上的,像unix domain socket,也可以是不同机器上的,像network socket。

本文着重介绍现在用的最多的 network socket,包括其在网络模型中的位置、API 的编程范式、常见错误等方面,最后用 Python 语言中的 socket API 实现几个实际的例子。Socket 中文一般翻译为“套接字”,不得不说这是个让人摸不着头脑的翻译,我也没想到啥“信达雅”的翻译,所以本文直接用其英文表述。本文中所有代码均可在 socket.py https://github.com/jiacai2050/socket.py仓库中找到。

0 Socket库

0.1 什么是 Socket?

Socket又称"套接字",应用程序通常通过"套接字"向网络发出请求或者应答网络请求,使主机间或者一台计算机上的进程间可以通讯。

0.2 socket()函数

Python 中,我们用 socket()函数来创建套接字,语法格式如下:

socket.socket([family[, type[, proto]]])

参数

  • family: 套接字家族可以使AF_UNIX或者AF_INET
  • type: 套接字类型可以根据是面向连接的还是非连接分为SOCK_STREAM或SOCK_DGRAM
  • protocol: 一般不填默认为0.
0.3 Socket 对象(内建)方法
函数描述
服务器端套接字
s.bind()绑定地址(host,port)到套接字, 在AF_INET下,以元组(host,port)的形式表示地址。
s.listen()开始TCP监听。backlog指定在拒绝连接之前,操作系统可以挂起的最大连接数量。该值至少为1,大部分应用程序设为5就可以了。
s.accept()被动接受TCP客户端连接,(阻塞式)等待连接的到来
客户端套接字
s.connect()主动初始化TCP服务器连接,。一般address的格式为元组(hostname,port),如果连接出错,返回socket.error错误。
s.connect_ex()connect()函数的扩展版本,出错时返回出错码,而不是抛出异常
公共用途的套接字函数
s.recv()接收TCP数据,数据以字符串形式返回,bufsize指定要接收的最大数据量。flag提供有关消息的其他信息,通常可以忽略。
s.send()发送TCP数据,将string中的数据发送到连接的套接字。返回值是要发送的字节数量,该数量可能小于string的字节大小。
s.sendall()完整发送TCP数据,完整发送TCP数据。将string中的数据发送到连接的套接字,但在返回之前会尝试发送所有数据。成功返回None,失败则抛出异常。
s.recvfrom()接收UDP数据,与recv()类似,但返回值是(data,address)。其中data是包含接收数据的字符串,address是发送数据的套接字地址。
s.sendto()发送UDP数据,将数据发送到套接字,address是形式为(ipaddr,port)的元组,指定远程地址。返回值是发送的字节数。
s.close()关闭套接字
s.getpeername()返回连接套接字的远程地址。返回值通常是元组(ipaddr,port)。
s.getsockname()返回套接字自己的地址。通常是一个元组(ipaddr,port)
s.setsockopt(level,optname,value)设置给定套接字选项的值。
s.getsockopt(level,optname[.buflen])返回套接字选项的值。
s.settimeout(timeout)设置套接字操作的超时期,timeout是一个浮点数,单位是秒。值为None表示没有超时期。一般,超时期应该在刚创建套接字时设置,因为它们可能用于连接的操作(如connect())
s.gettimeout()返回当前超时期的值,单位是秒,如果没有设置超时期,则返回None。
s.fileno()返回套接字的文件描述符。
s.setblocking(flag)如果flag为0,则将套接字设为非阻塞模式,否则将套接字设为阻塞模式(默认值)。非阻塞模式下,如果调用recv()没有发现任何数据,或send()调用无法立即发送数据,那么将引起socket.error异常。
s.makefile()创建一个与该套接字相关连的文件
0.4 简单实例
服务端

我们使用 socket 模块的 socket 函数来创建一个 socket 对象。socket 对象可以通过调用其他函数来设置一个 socket 服务。

现在我们可以通过调用 bind(hostname, port) 函数来指定服务的 port(端口)。

接着,我们调用 socket 对象的 accept 方法。该方法等待客户端的连接,并返回 connection 对象,表示已连接到客户端。

完整代码如下:

#!/usr/bin/python
# -*- coding: UTF-8 -*-
# 文件名:server.py
 
import socket               # 导入 socket 模块
 
s = socket.socket()         # 创建 socket 对象
host = socket.gethostname() # 获取本地主机名
port = 12345                # 设置端口
s.bind((host, port))        # 绑定端口
 
s.listen(5)                 # 等待客户端连接
while True:
    c,addr = s.accept()     # 建立客户端连接
    print '连接地址:', addr
    c.send('欢迎访问菜鸟教程!')
    c.close()                # 关闭连接
客户端

接下来我们写一个简单的客户端实例连接到以上创建的服务。端口号为 12345。

socket.connect(hosname, port ) 方法打开一个 TCP 连接到主机为 hostname 端口为 port 的服务商。连接后我们就可以从服务端获取数据,记住,操作完成后需要关闭连接。

完整代码如下:

#!/usr/bin/python
# -*- coding: UTF-8 -*-
# 文件名:client.py
 
import socket               # 导入 socket 模块
 
s = socket.socket()         # 创建 socket 对象
host = socket.gethostname() # 获取本地主机名
port = 12345                # 设置端口号
 
s.connect((host, port))
print s.recv(1024)
s.close()

现在我们打开两个终端,第一个终端执行 server.py 文件:

$ python server.py

第二个终端执行 client.py 文件:

$ python client.py 

这时我们再打开第一个终端,就会看到有以下信息输出:

连接地址: ('192.168.0.118', 62461)
0.5 Python Internet 模块

以下列出了 Python 网络编程的一些重要模块:

协议功能用处端口号Python 模块
HTTP网页访问80httplib, urllib, xmlrpclib
NNTP阅读和张贴新闻文章,俗称为"帖子"119nntplib
FTP文件传输20ftplib, urllib
SMTP发送邮件25smtplib
POP3接收邮件110poplib
IMAP4获取邮件143imaplib
Telnet命令行23telnetlib
Gopher信息查找70gopherlib, urllib

更多内容可以参阅官网的 Python Socket Library and Modules。
https://docs.python.org/2/library/socket.html

2 篇笔记

关于简单实例 Mac 上运行不出来,有几个报错,做了下修改。

server.py

#!/usr/bin/python
# -*- coding: UTF-8 -*-

import socket
# 建立一个服务端
server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
server.bind(('localhost',6999)) #绑定要监听的端口
server.listen(5) #开始监听 表示可以使用五个链接排队
while True:# conn就是客户端链接过来而在服务端为期生成的一个链接实例
    conn,addr = server.accept() #等待链接,多个链接的时候就会出现问题,其实返回了两个值
    print(conn,addr)
    while True:
        try:
            data = conn.recv(1024)  #接收数据
            print('recive:',data.decode()) #打印接收到的数据
            conn.send(data.upper()) #然后再发送数据
        except ConnectionResetError as e:
            print('关闭了正在占线的链接!')
            break
    conn.close()

client.py

#!/usr/bin/python
# -*- coding: UTF-8 -*-

import socket# 客户端 发送一个数据,再接收一个数据
client = socket.socket(socket.AF_INET,socket.SOCK_STREAM) #声明socket类型,同时生成链接对象
client.connect(('localhost',6999)) #建立一个链接,连接到本地的6969端口
while True:
    # addr = client.accept()
    # print '连接地址:', addr
    msg = '欢迎访问菜鸟教程!'  #strip默认取出字符串的头尾空格
    client.send(msg.encode('utf-8'))  #发送一条信息 python3 只接收btye流
    data = client.recv(1024) #接收一个信息,并指定接收的大小 为1024字节
    print('recv:',data.decode()) #输出我接收的信息
client.close() #关闭这个链接

这个可以的。

关于简单实例都修改。

服务端:

#!/usr/bin/python
# -*- coding: UTF-8 -*-

import sys
reload(sys)
sys.setdefaultencoding('utf8')
import socket
# 建立一个服务端
server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
server.bind(('localhost',9090)) #绑定要监听的端口
server.listen(5) #开始监听 表示可以使用五个链接排队
while True:# conn就是客户端链接过来而在服务端为期生成的一个链接实例
    conn,addr = server.accept() #等待链接,多个链接的时候就会出现问题,其实返回了两个值
    print(conn,addr)
    while True:
        data = conn.recv(1024)  #接收数据
        print('recive:',data.decode()) #打印接收到的数据
        conn.send(data.upper()) #然后再发送数据
    conn.close()

客户端:

#!/usr/bin/python
# -*- coding: UTF-8 -*-

import sys
reload(sys)
sys.setdefaultencoding('utf8')
import socket# 客户端 发送一个数据,再接收一个数据
client = socket.socket(socket.AF_INET,socket.SOCK_STREAM) #声明socket类型,同时生成链接对象
client.connect(('localhost',9090)) #建立一个链接,连接到本地的6969端口
while True:
    # addr = client.accept()
    # print '连接地址:', addr
    msg = '欢迎访问菜鸟教程!'  #strip默认取出字符串的头尾空格
    client.send(msg.encode('utf-8'))  #发送一条信息 python3 只接收btye流
    data = client.recv(1024) #接收一个信息,并指定接收的大小 为1024字节
    print('recv:',data.decode()) #输出我接收的信息
client.close() #关闭这个链接

1 概述

Socket 作为一种通用的技术规范,首次是由 Berkeley 大学在 1983 为 4.2BSD Unix 提供的,后来逐渐演化为 POSIX 标准。Socket API 是由操作系统提供的一个编程接口,让应用程序可以控制使用 socket 技术。Unix 哲学中有一条一切皆为文件,所以 socket 和 file 的 API 使用很类似:可以进行read、write、open、close等操作。

现在的网络系统是分层的,理论上有OSI模型,工业界有TCP/IP协议簇。其对比如下:
在这里插入图片描述

osi vs tcp/ip

每层上都有其相应的协议,socket API 不属于TCP/IP协议簇,只是操作系统提供的一个用于网络编程的接口,工作在应用层与传输层之间:

where socket works in tcp/ip
where socket works in tcp/ip

我们平常浏览网站所使用的http协议,收发邮件用的smtp与imap,都是基于 socket API 构建的。

一个 socket,包含两个必要组成部分:

地址,由 ip 与 端口组成,像192.168.0.1:80。
协议,socket 所是用的传输协议,目前有三种:TCP、UDP、raw IP。
地址与协议可以确定一个socket;一台机器上,只允许存在一个同样的socket。TCP 端口 53 的 socket 与 UDP 端口 53 的 socket 是两个不同的 socket。

根据 socket 传输数据方式的不同(使用协议不同),可以分为以下三种:

  • Stream sockets,也称为“面向连接”的 socket,使用 TCP 协议。实际通信前需要进行连接,传输的数据没有特定的结构,所以高层协议需要自己去界定数据的分隔符,但其优势是数据是可靠的。
  • Datagram sockets,也称为“无连接”的 socket,使用 UDP 协议。实际通信前不需要连接,一个优势时 UDP 的数据包自身是可分割的(self-delimiting),也就是说每个数据包就标示了数据的开始与结束,其劣势是数据不可靠。
  • Raw sockets,通常用在路由器或其他网络设备中,这种 socket 不经过TCP/IP协议簇中的传输层(transport layer),直接由网络层(Internet layer)通向应用层(Application layer),所以这时的数据包就不会包含 tcp 或 udp 头信息。

数据包在各个层间的变更
数据包在各个层间的变更

2 Python socket API

Python 里面用(ip, port)的元组来表示 socket 的地址属性,用AF_*来表示协议类型。
数据通信有两组动词可供选择:send/recv 或 read/write。read/write 方式也是 Java 采用的方式,这里不会对这种方式进行过多的解释,但是需要注意的是:

read/write 操作的具有 buffer 的“文件”,所以在进行读写后需要调用flush方法去真正发送或读取数据,否则数据会一直停留在缓冲区内。

2.1 TCP socket

TCP socket 由于在通信前需要建立连接,所以其模式较 UDP socket 复杂些。具体如下:

TCP socket API
TCP socket API

API 的具体含义这里不在赘述,可以查看手册,https://en.wikipedia.org/wiki/Berkeley_sockets#Socket_API_functions
这里给出 Python 语言实现的 echo server。

echo_server.py https://github.com/jiacai2050/socket.py/blob/master/simple_tcp_echo/echo_server.py

import socket

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

def handler(client_sock, addr):
    try:
        print('new client from %s:%s' % addr)
        msg = client_sock.recv(100)
        client_sock.send(msg)
        print('received data[%s] from %s:%s' % ((msg,) + addr))
    finally:
        client_sock.close()
        print('client[%s:%s] socket closed' % addr)

if __name__ == '__main__':
    # 设置 SO_REUSEADDR 后,可以立即使用 TIME_WAIT 状态的 socket
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    sock.bind(('', 5500))
    sock.listen(5)

    while 1:
        client_sock, addr = sock.accept()
        handler(client_sock, addr)

echo_client.py https://github.com/jiacai2050/socket.py/blob/master/simple_tcp_echo/echo_client.py

import socket

if __name__ == '__main__':
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    data_to_sent = 'hello tcp socket'
    try:
        sock.connect(('', 5500))

        sent = sock.send(data_to_sent)
        print(sock.recv(1024))
    finally:
        sock.close()
print('socket closed')

上面代码有一点需要注意:server 端的 socket 设置了SO_REUSEADDR为1,目的是可以立即使用处于TIME_WAIT状态的socket,那么TIME_WAIT又是什么意思呢?后面在讲解 tcp 状态机时再做详细介绍。

2.2 UDP socket

udp_socket_api
udp_socket_api

UDP 版的 socket server 的代码在进行bind后,无需调用listen方法。

udp_echo_server.py https://github.com/jiacai2050/socket.py/blob/master/simple_udp_echo/echo_server.py

import socket

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 设置 SO_REUSEADDR 后,可以立即使用 TIME_WAIT 状态的 socket
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(('', 5500))
# 没有调用 listen

if __name__ == '__main__':
    while 1:
        data, addr = sock.recvfrom(1024)

        print('new client from %s:%s' % addr)
        sock.sendto(data, addr)

udp_echo_client.py https://github.com/jiacai2050/socket.py/blob/master/simple_udp_echo/echo_client.py


import socket

udp_server_addr = ('', 5500)

if __name__ == '__main__':
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    data_to_sent = 'hello udp socket'
    try:
        sent = sock.sendto(data_to_sent, udp_server_addr)
        data, server = sock.recvfrom(1024)
        print('receive data:[%s] from %s:%s' % ((data,) + server))
    finally:
        sock.close()

3 常见陷阱

3.1 忽略返回值

本文中的 echo server 示例因为篇幅限制,也忽略了返回值。网络通信是个非常复杂的问题,通常无法保障通信双方的网络状态,很有可能在发送/接收数据时失败或部分失败。所以有必要对发送/接收函数的返回值进行检查。本文中的 tcp echo client 发送数据时,正确写法应该如下:

total_send = 0
content_length = len(data_to_sent)
while total_send < content_length:
    sent = sock.send(data_to_sent[total_send:])
    if sent == 0:
        raise RuntimeError("socket connection broken")
    total_send += total_send + sent

同理,接收数据时也应该检查返回值:

chunks = []
bytes_recd = 0
while bytes_recd < MSGLEN:   # MSGLEN 为实际数据大小
    chunk = self.sock.recv(min(MSGLEN - bytes_recd, 2048))
    if chunk == b'':
        raise RuntimeError("socket connection broken")
    chunks.append(chunk)
    bytes_recd = bytes_recd + len(chunk)
return b''.join(chunks)

send/recv操作的是网络缓冲区的数据,它们不必处理传入的所有数据。

一般来说,当网络缓冲区填满时,send函数就返回了;当网络缓冲区被清空时,recv 函数就返回。

可以通过下面的方式设置缓冲区大小。

s.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, buffer_size)  # 发送
s.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, buffer_size)  # 接受
3.2 误认为 TCP 具有 framing

TCP 不提供 framing,这使得其很适合于传输数据流。这是其与 UDP 的重要区别之一。UDP 是一个面向消息的协议,能保持一条消息在发送者与接受者之间的完备性。

Framing capabilities of UDP and the lack of framing in TCP
Framing capabilities of UDP and the lack of framing in TCP

代码示例参考:framing_assumptions
https://github.com/jiacai2050/socket.py/tree/master/framing_assumptions

4 TCP 的状态机

在前面echo server 的示例中,提到了TIME_WAIT状态,为了正式介绍其概念,需要了解下 TCP 从生成到结束的状态机。(图片来源)

tcp_state transition
tcp_state transition

这个状图转移图非常非常关键,也比较复杂,总共涉及了 11 种状态。我自己为了方便记忆,对这个图进行了拆解,仔细分析这个图,可以得出这样一个结论:

连接的打开与关闭有被动(passive)与主动(active)两种情况。主动关闭时,涉及到的状态转移最多,包括FIN_WAIT_1、FIN_WAIT_2、CLOSING、TIME_WAIT。(是不是有种 no zuo no die 的感觉)

此外,由于 TCP 是可靠的传输协议,所以每次发送一个数据包后,都需要得到对方的确认(ACK),有了上面这两个知识后,再来看下面的图:(图片来源)

tcp 关闭时的状态转移时序图
tcp 关闭时的状态转移时序图

我们重点分析上图中链接断开的过程,其中主动关闭端为 Client,被动关闭端为 Server 。

  • Client 调用 close 方法的同时,会向 Server 发送一个 FIN,然后自己处于 FIN_WAIT_1 状态,在收到 server ACK 回应后变为 FIN_WAIT_2
  • Server 收到 FIN 后,向 Client 回复 ACK 确认,状态变化为 CLOSE_WAIT,然后开始进行一些清理工作
  • 在 Server 清理工作完成后,会调用close方法,这时向 Client 发送 FIN 信号,状态变化为 LAST_ACK
  • Client 接收到 FIN 后,状态由 FIN_WAIT_2 变化为 TIME_WAIT,同时向 Server 回复 ACK
  • Server 收到 ACK 后,状态变化为 CLOSE,表明 Server 端的 socket 已经关闭
  • 处于 TIME_WAIT 状态的 Client 不会立刻转为 CLOSED 状态,而是需要等待 2MSL(max segment life,一个数据包在网络传输中最大的生命周期),以确保 Server 能够收到最后发出的 ACK。如果 Server 没有收到最后的 ACK,那么 Server 就会重新发送 FIN,所以处于TIME_WAIT的 Client 会再次发送一个 ACK 信号,这么一来(FIN来)一回(ACK),正好是两个 MSL 的时间。如果等待的时间小于 2MSL,那么新的 socket 就可以收到之前连接的数据。

上面是正常逻辑时的关闭顺序,如果任意一步出现问题都会导致 Socket 状态变化出现问题,下面说几种常见的问题:

  • 在上述过程第二步,回复完 ACK 后,如果忘记调用 CLOSE 方法,那么 Server 端在会一直处于 CLOSE_TIME 状态,处于 FIN_WAIT_2 状态的 Client 端会在 60 秒后超时,直接关闭。这个问题的具体案例可参考《This is strictly a violation of the TCP specification》https://blog.cloudflare.com/this-is-strictly-a-violation-of-the-tcp-specification
  • 前面 echo server 的示例也说明了,处于 TIME_WAIT 并不是说一定不能使用,可以通过设置 socket 的 SO_REUSEADDR 属性以达到不用等待 2MSL 的时间就可以复用socket 的目的,当然,这仅仅适用于测试环境,正常情况下不要修改这个属性。

5 实战

5.1 HTTP UA

http 协议是如今万维网的基石,可以通过 socket API 来简单模拟一个浏览器(UA)是如何解析 HTTP 协议数据的。

import socket

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
baidu_ip = socket.gethostbyname('baidu.com')
sock.connect((baidu_ip, 80))
print('connected to %s' % baidu_ip)

req_msg = [
    'GET / HTTP/1.1',
    'User-Agent: curl/7.37.1',
    'Host: baidu.com',
    'Accept: */*',
]
delimiter = '\r\n'

sock.send(delimiter.join(req_msg))
sock.send(delimiter)
sock.send(delimiter)

print('%sreceived%s' % ('-'*20, '-'*20))
http_response = sock.recv(4096)
print(http_response)

运行上面的代码可以得到下面的输出

--------------------received--------------------
HTTP/1.1 200 OK
Date: Tue, 01 Nov 2016 12:16:53 GMT
Server: Apache
Last-Modified: Tue, 12 Jan 2010 13:48:00 GMT
ETag: "51-47cf7e6ee8400"
Accept-Ranges: bytes
Content-Length: 81
Cache-Control: max-age=86400
Expires: Wed, 02 Nov 2016 12:16:53 GMT
Connection: Keep-Alive
Content-Type: text/html

<html>
<meta http-equiv="refresh" content="0;url=http://www.baidu.com/">
</html>

http_response是通过直接调用recv(4096)得到的,万一真正的返回大于这个值怎么办?我们前面知道了 TCP 协议是面向流的,它本身并不关心消息的内容,需要应用程序自己去界定消息的边界,对于应用层的 HTTP 协议来说,有几种情况,最简单的一种时通过解析返回值头部的Content-Length属性,这样就知道body的大小了,对于 HTTP 1.1版本,支持Transfer-Encoding: chunked传输,对于这种格式,这里不在展开讲解,大家只需要知道, TCP 协议本身无法区分消息体就可以了。对这块感兴趣的可以查看 CPython 核心模块 http.client

5.2 Unix_domain_socket

UDS 用于同一机器上不同进程通信的一种机制,其API适用与 network socket 很类似。只是其连接地址为本地文件而已。

代码示例参考:uds_server.py、uds_client.py https://github.com/jiacai2050/socket.py/blob/master/in_action/uds_server.py

5.3 ping

ping 命令作为检测网络联通性最常用的工具,其适用的传输协议既不是TCP,也不是 UDP,而是 ICMP。
ICMP 消息(messages)通常用于诊断 IP 协议产生的错误,traceroute 命令也是基于 ICMP 协议实现。利用 Python raw sockets API 可以模拟发送 ICMP 消息,实现类似 ping 的功能。

代码示例参考:ping.py https://github.com/jiacai2050/socket.py/blob/master/in_action/ping.py

5.4 netstat vs ss

netstat 与 ss 都是类 Unix 系统上查看 Socket 信息的命令。netstat 是比较老牌的命令,常用的选择有

  • -t,只显示 tcp 连接
  • -u,只显示 udp 连接
  • -n,不用解析hostname,用 IP 显示主机,可以加快执行速度
  • -p,查看连接的进程信息
  • -l,只显示监听的连接

ss 是新兴的命令,其选项和 netstat 差不多,主要区别是能够进行过滤(通过state与exclude关键字)。

$ ss -o state time-wait -n | head
Recv-Q Send-Q             Local Address:Port               Peer Address:Port
0      0                 10.200.181.220:2222              10.200.180.28:12865  timer:(timewait,33sec,0)
0      0                      127.0.0.1:45977                 127.0.0.1:3306   timer:(timewait,46sec,0)
0      0                      127.0.0.1:45945                 127.0.0.1:3306   timer:(timewait,6.621ms,0)
0      0                 10.200.181.220:2222              10.200.180.28:12280  timer:(timewait,12sec,0)
0      0                 10.200.181.220:2222              10.200.180.28:35045  timer:(timewait,43sec,0)
0      0                 10.200.181.220:2222              10.200.180.28:42675  timer:(timewait,46sec,0)
0      0                      127.0.0.1:45949                 127.0.0.1:3306   timer:(timewait,11sec,0)
0      0                      127.0.0.1:45954                 127.0.0.1:3306   timer:(timewait,21sec,0)
0      0               ::ffff:127.0.0.1:3306           ::ffff:127.0.0.1:45964  timer:(timewait,31sec,0)

这两个命令更多用法可以参考:

6 总结

我们的生活已经离不开网络,平时的开发也充斥着各种复杂的网络应用,从最基本的数据库,到各种分布式系统,不论其应用层怎么复杂,其底层传输数据的的协议簇是一致的。Socket 这一概念我们很少直接与其打交道,但是当我们的系统出现问题时,往往是对底层的协议认识不足造成的,希望这篇文章能对大家编程网络方面的程序有所帮助。

7. SocketTool 编程调试工具

作为网络工程师,我们经常需要在本地电脑上建立Socket服务端或客户端来测试软件,比如建立Socket服务端,就可以等待网络客户端软件的连接,通过和客户端进行通信来测试客户端软件是否正常;同时也可以建立Socket客户端来连接对方的测试服务器软件,来测试对方的服务器软件是否正常。
在这里插入图片描述

工具/原料

SocketTool

方法/步骤
1 在百度中搜索SocketTool可以找到该测试软件的下载地址。

在这里插入图片描述

2 SocketTool怎么用/如何建立Socket服务端/客户端

在这里插入图片描述
下载完成后,软件是一个单独的运行程序,可以直接打开软件。

3 软件的界面很简单,在左侧有tcp和udp的客户端或服务端的快捷按钮,上方有【创建】【删除】【退出】等选项按钮。

在这里插入图片描述

4 我们先来建立TCP的测试服务端。点击【TCP Server】再点击【创建】。

在这里插入图片描述
选择一个监听端口,这里我们使用6001作为服务端的监听端口。
在这里插入图片描述
建立完成后,服务端会自动启动,软件会显示【启动监听】的状态。
在这里插入图片描述
我们可以检测一下本机的6001端口是否已经打开。在DOS窗口中输入命令【netstat -a】,可以在列表中看到本机的6001端口的状态为listening的状态,表示本机的6001端口正处于监听的状态。
在这里插入图片描述
在DOS窗口中输入命令【telnet 192.168.0.140 6001】来登录本地的6001端口。
在这里插入图片描述
点击回车键,就可以成功登录6001端口。在测试软件中就可以看到状态是已连接的状态,同时也可以看到对方的ip就是本地ip。
在这里插入图片描述
在这里插入图片描述
再来测试通信情况,在DOS窗口中输入a、b、c,在软件的接收窗口就可以看到收到的数据了。
在这里插入图片描述
在软件的发送窗口中输入1234567890,点击发送后,在DOS窗口中就可以看到软件发送过来的数据了。
在这里插入图片描述
测试完成后,在软件中点击【停止监听】,同时在DOS窗口中可以看到【失去了跟主机的连接】,表示测试连接已经断开。
在这里插入图片描述
在这里插入图片描述
再来创建TCP的客户端,点击【TCP Client】再点击【创建】。会弹出【创建socket客户端】窗口,输入对方的ip和对方的端口,点击确认。
在这里插入图片描述
tcp的客户端已经建立好,如果对方的端口监听正常的话,点击【连接】就可以连接到对方的端口和对方进行测试通信了。
在这里插入图片描述

Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐