网络编程之Socket代码实例

网络编程之Socket代码实例

1、基本Socket例子

Server端:python

# Echo server program
import socket

HOST = ''                 # Symbolic name meaning all available interfaces
PORT = 50007              # Arbitrary non-privileged port

sock_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock_server.bind((HOST, PORT))

sock_server.listen(1) #开始监听,1表明在容许有一个链接排队,更多的新链接连进来时就会被拒绝
conn, addr = sock_server.accept() #阻塞直到有链接为止,有了一个新链接进来后,就会为这个请求生成一个链接对象

with conn:
    print('Connected by', addr)
    while True:
        data = conn.recv(1024) #接收1024个字节
        if not data: break #收不到数据,就break
        conn.sendall(data) #把收到的数据再所有返回给客户端

Client端:linux

# Echo client program
import socket

HOST = 'localhost'    # The remote host
PORT = 50007              # The same port as used by the server

client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect((HOST, PORT))
client.sendall(b'Hello, world')

data = client.recv(1024)

print('Received',data)

先启动Server端,再启动Client端,结果以下:编程

2、循环收发数据

第一次接触就这么交待了,之说了一句话,感受不够过瘾,如何实现更多的交互呢?简单,只须要让客户端不断的发,服务端不断的收就能够了,写个循环搞定。windows

Server端:服务器

# Echo server program
import socket

HOST = ''                 # Symbolic name meaning all available interfaces
PORT = 50007              # Arbitrary non-privileged port

sock_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock_server.bind((HOST, PORT))

sock_server.listen(1) #开始监听,1表明在容许有一个链接排队,更多的新链接连进来时就会被拒绝
conn, addr = sock_server.accept() #阻塞直到有链接为止,有了一个新链接进来后,就会为这个请求生成一个链接对象

with conn:
    print('Connected by', addr)
    while True:
        data = conn.recv(1024) #接收1024个字节
        print("server recv:",conn.getpeername(), data.decode())
        if not data: break #收不到数据,就break
        conn.sendall(data) #把收到的数据再所有返回给客户端

Client端:cookie

# Echo client program
import socket

HOST = 'localhost'    # The remote host
PORT = 50007              # The same port as used by the server

client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect((HOST, PORT))

while True: 

    msg = input(">>>:").strip()
    if len(msg) == 0:continue

    client.sendall(msg.encode()) #发送用户输入的数据,必须是bytes模式

    data = client.recv(1024)

    print('Received',data.decode()) #收到服务器的响应后,decode一下

3、简单聊天软件

上面的例子,服务端只是将客户端发来的再发送给客户端,这哪叫聊天啊,这种事须要双方配合,得让服务端也能说话。网络

Server端:并发

import socket

HOST = ''                 # Symbolic name meaning all available interfaces
PORT = 50007              # Arbitrary non-privileged port

sock_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock_server.bind((HOST, PORT))

sock_server.listen(1) #开始监听,1表明在容许有一个链接排队,更多的新链接连进来时就会被拒绝
conn, addr = sock_server.accept() #阻塞直到有链接为止,有了一个新链接进来后,就会为这个请求生成一个链接对象

with conn:
    print('Connected by', addr)
    while True:
        data = conn.recv(1024) #接收1024个字节
        print("recv from Alex:",conn.getpeername(), data.decode())
        if not data: break #收不到数据,就break

        response = input(">>>").strip()
        conn.send(response.encode())
        print("send to alex:",response)

Client不须要作更改,直接看结果:socket

以上的例子仍是有bug,双方只能一来一往的说话,若是你想来纳许发2句话是不行的,会卡住。这是由于你发了一条消息后,就去调用recv方法接收服务器的响应了,再服务器端返回消息以前,这个recv(1024)方法是阻塞的,若是想容许此时还能再发消息给服务器端,就须要再单独启动一个线程,只负责发消息。tcp

4、聊天软件升级版

刚才在聊天的时候,服务端在服务客户端的时候,其它人若是也想跟服务端链接是处于排队状态,而后等正在被服务的客户端完事并断开后,下一我的就跟上,但实际状况是客户端一断开,服务端也跟着断了。

为何会断呢?引文服务端如下代码的意思是,若是收不到数据,就跳出循环,就断开了。

conn, addr = sock_server.accept() #阻塞直到有链接为止,有了一个新链接进来后,就会为这个请求生成一个链接对象

with conn:
    print('Connected by', addr)
    while True:
        data = conn.recv(1024) #接收1024个字节
        print("recv from Alex:",conn.getpeername(), data.decode())

        if not data: break #收不到数据,就break , 就是它干的

        response = input(">>>").strip()
        conn.send(response.encode())
        print("send to alex:",response)

想实现一个客户端断开后,能够马上接入另一个客户端的话,怎么办呢?只须要再在外层加个循环。

while True: #最外层loop 

    conn, addr = sock_server.accept() #阻塞直到有链接为止,有了一个新链接进来后,就会为这个请求生成一个链接对象
    #为什么把上面这句话也包含在循环里?
    print("来了个新客人",conn.getpeername() )

    with conn:
        print('Connected by', addr)
        while True:
            data = conn.recv(1024) #接收1024个字节
            print("recv from :",conn.getpeername(), data.decode())
            if not data: break #收不到数据,就break
            conn.send(data.upper())
            print("send to alex:",data)

break 跳出后就回到大while那层:

可是,有的人在重启服务端时可能会遇到:

这是因为你的服务端仍然存在4次挥手的time_wait状态,在占用地址(若是不懂,请深刻研究:一、tcp三次握手,四次挥手。二、sun洪水攻击。三、服务器高并发状况下会有大量的time_wait状态的优化方法)

解决方法1:

sock_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock_server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) #一行代码搞定,写在bind以前
sock_server.bind((HOST, PORT))

解决方法2(用于Linux系统):

发现系统存在大量TIME_WAIT状态的链接,经过调整linux内核参数解决,
vi /etc/sysctl.conf

编辑文件,加入如下内容:
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_fin_timeout = 30

而后执行 /sbin/sysctl -p 让参数生效。

net.ipv4.tcp_syncookies = 1 表示开启SYN Cookies。当出现SYN等待队列溢出时,启用cookies来处理,可防范少许SYN攻击,默认为0,表示关闭;

net.ipv4.tcp_tw_reuse = 1 表示开启重用。容许将TIME-WAIT sockets从新用于新的TCP链接,默认为0,表示关闭;

net.ipv4.tcp_tw_recycle = 1 表示开启TCP链接中TIME-WAIT sockets的快速回收,默认为0,表示关闭。

net.ipv4.tcp_fin_timeout 修改系統默认的 TIMEOUT 时间

5、UDP实例

UDP不须要通过3次握手和4次挥手,不须要提早创建链接,直接发数据就行。

Server端:

import socket
ip_port=('127.0.0.1',9000)
BUFSIZE=1024
udp_server_client=socket.socket(socket.AF_INET,socket.SOCK_DGRAM) #udp类型

udp_server_client.bind(ip_port)

while True:
    msg,addr=udp_server_client.recvfrom(BUFSIZE)
    print("recv ",msg,addr)

    udp_server_client.sendto(msg.upper(),addr)

Client端:

import socket
ip_port = ('127.0.0.1',9000)
BUFSIZE = 1024
udp_server_client = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)

while True:
    msg=input('>>: ').strip()
    if not msg:continue
    udp_server_client.sendto(msg.encode('utf-8'),ip_port)

    back_msg,addr = udp_server_client.recvfrom(BUFSIZE)
    print(back_msg.decode('utf-8'),addr)

结果:

6、TCP  VS  UDP

一、TCP基于连接通讯

  • 基于连接,则须要listen(backlog),指定链接池的大小。
  • 基于连接,必须先运行服务端,而后再由客户端发起连接请求。
  • 对于mac系统:若是一端断开了连接,那另一端的连接也跟着完蛋,recv将不会阻塞,接收到的是空(解决方法:服务端通讯循环内加异常处理,捕捉到异常后就break通信循环)
  • 对于windows/Linux系统:若是一端断开了连接,那另一端的连接也跟着完蛋,recv将不会阻塞,收到的是空(解决方法:服务端通讯循环内加异常处理,捕捉到异常后就break通信循环)

二、UDP无连接

  • 无连接,于是无需listen(backlog),更加没有什么链接池之说了。
  • 无连接,UDP的sendinto不用管是否有一个正在运行的服务端,能够己端一个劲地发消息,只不过数据会丢失。
  • recvfrom收的数据小于sendinto发送的数据时,在mac和Linux系统上数据直接丢失,在windows系统则会直接报错。
  • 只有sendinto发送数据没有recvfrom收数据,则数据丢失。
相关文章
相关标签/搜索