Socket网络编程

某个设备上的应用怎么跟其他设备进行交换,这就是这次的内容。

要学习这次内容,我们需先了解OSI七层网络模型、TCP/IP三次握手、UDP

1、OSI七层网络模型、TCP/IP、三次握手、UDP

在这里插入图片描述
我们大部分开发软件和系统都属于应用层的范畴

TCP三次握手

在TCP/IP协议中,TCP协议提供可靠的连接服务,采用三次握手建立一个连接.

第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认;
SYN:同步序列编号(Synchronize Sequence Numbers)

第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;

第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手.

UDP

UDP 是User Datagram Protocol的简称, 中文名是用户数据报协议,是OSI(Open System Interconnection,开放式系统互联) 参考模型中一种无连接的传输层协议,提供面向事务的简单不可靠信息传送服务,IETF RFC 768是UDP的正式规范。

2、Socket套接字

套接字(Socket),就是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象。

一个套接字就是网络上进程通信的一端,提供了应用层进程利用网络协议交换数据的机制。

从所处的地位来讲,套接字上联应用进程,下联网络协议栈,是应用程序通过网络协议进行通信的接口,是应用程序与网络协议根进行交互的接口

Socket套接字是处在应用层和运输层之间的,能够帮我们抽象出TCP或UDP的协议,能够帮我们指定IP地址的端对端的发送。

用户进程(应用程序)是通过创建一个Socket端口,然后才能与网络进行通信,去连接到网络上其他的用户进程。

python提供了socket模块、socketserver模块

服务端与客户端在网络上交换数据时,都需要创建Spcket才能进行数据交换。Socket也分为服务端Socket和客户端Socket
在这里插入图片描述

3、TCP/IP协议、各层次通信协议简图

在这里插入图片描述

4、B/S架构和C/S架构

S: Sever 服务端

CS: Client/Server(客户机/服务器)结构
BS: Browser/Server(浏览器/服务器)结构

标准网络编程用的都是C/S架构,比如QQ、微信、端游

B/S 浏览器:ie,火狐,谷歌(Google Chrome)

B/S架构减轻了工作量,但是B/S架构只能在浏览器,http协议,html等等这一系列标准下去运行,做一些特殊处理是有一定的障碍,而且有一些私有化数据的安全性要求的话,浏览器更容易被攻破。所以更需要一些安全漏洞的检查。但是B/S架构的优点还是远远大于缺点的,所以应用广泛。

5、TCP和UDP

都是传输协议

服务端server& 客户端client

服务端一般就是装在机房里的服务器,客户端就是装在自己电脑和手机上的应用程序。

TCP是安全的,或者说传输相对UDP是安全的,它经过三次握手之后这个链接是可以被信任的,确保客户端和服务端的通信时可以完成的。

UDP就是相当于我就对着你扔出去一些消息,你不需要告诉我你收没收到,所以这个连接相对来说是不安全的,也就是说客户端发送给服务端的消息,你收没收到,客户端是不管的。

所以要根据不同的应用场景来选择不同的传输协议,若更在意系统的性能,对收没收到消息反而不是很在意,此时就应该选择UDP。
如果是局域网,可以用UDP来做一些应用;互联网的话还是使用TCP。

TCP的Server端和Client端

TCP建立链接的一些要求,有发就有收,收发必须相等
在这里插入图片描述
Client事先知道Server的ip地址和端口

6、Python网络编程模块

socket、 socketserver

服务器端要先运行

1、将小写转化为大写

服务端

import socket    # 引入socket模块

hostaddress = ("127.0.0.1", 8888)   # 定义本机地址,端口,IP地址代表一台服务器,端口号代表运行在终端上的某一个应用程序

# 使用ipv4,使用TCP协议
sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # 创建一个socket对象
sk.bind(hostaddress)  # 绑定
sk.listen(5)    # 监听,默认是1
print("启动socket服务,等待客户端连接...")

conn, clientaddress = sk.accept()  # 阻塞状态,等到客户端一个连接的对象

# bytes 字节数组
data = conn.recv(1024).decode()  # 接受客户端发来的内容,长度,decode方法转化为字符串

print("接收到客户端 %s 发送来的信息 : %s" % (clientaddress, data))

res = data.upper()  # 发来的字母转换为全大写

# str
conn.sendall(res.encode())   # 将字符串内容返回给客户端,encode转换为字节数组,字符串不能在网络上传输

conn.close()

在这里插入图片描述

客户端


import socket

serveraddress = ("127.0.0.1", 8888)

sk = socket.socket()

sk.connect(serveraddress)   # 连接ip地址

sss = 'abc'

sk.sendall(sss.encode())  # 转成bytes字节数组才能发送

answer = sk.recv(1024).decode()   # 应答,decode转换成字节

print("收到服务器应答:%s" % answer)

sk.close()  # 关闭连接

在这里插入图片描述
在这里插入图片描述
服务端指定了监听的端口,客户端不需要指定,因为客户端只需要知道服务器端是谁,自己随便找一个未被占用的端口来跟服务器通信,最后close释放掉就可以了,操作系统会帮我们处理掉

2、代码优化一下,具有多次收发消息的能力

引入循环,while True(无限循环或者死循环)

服务端


import socket    # 引入socket模块

hostaddress = ("127.0.0.1", 8888)   # 定义本机地址,端口,IP地址代表一台服务器,端口号代表运行在终端上的某一个应用程序

# 使用ipv4,使用TCP协议
sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # 创建一个socket对象
sk.bind(hostaddress)  # 绑定
sk.listen(5)    # 监听,默认是1
print("启动socket服务,等待客户端连接...")

conn, clientaddress = sk.accept()  # 阻塞状态,等到客户端一个连接的对象

while True:
    # bytes 字节数组
    data = conn.recv(1024).decode()  # 接受客户端发来的内容,长度,decode方法转化为字符串

    if data == 'exit':
        print("客户端发送完成, 断开连接。")
        break

    print("接收到客户端 %s 发送来的信息 : %s" % (clientaddress, data))

    res = data.upper()  # 处理,发来的字母转换为全大写

    # str
	conn.sendall(res.encode())   # 应答,将字符串内容返回给客户端,encode转换为字节数组,字符串不能在网络上传输

conn.close()

客户端


import socket

serveraddress = ("127.0.0.1", 8888)

sk = socket.socket()

sk.connect(serveraddress)   # 连接ip地址

while True:
    sss = input('发送内容:').strip()  # 去空格

    sk.sendall(sss.encode())  # 转成bytes字节数组才能发送

    if sss == 'exit':
        print('客户端退出连接.')
        break

    answer = sk.recv(1024).decode()   # 应答,decode转换成字节

    print("收到服务器应答:%s" % answer)

sk.close()  # 关闭连接

在这里插入图片描述
在这里插入图片描述
但是只能连接一个客户,其他客户连接能发送但是没有响应

3、继续优化,服务端可以接受多个客户端的请求

引入多线程 同时干多件事


import socket    # 引入socket模块
import threading   # 引入多线程


def deal(link, client):
    print('新线程开始处理客户端 %s: %s 的数据请求' % client)
    while True:
        # bytes 字节数组
        data = link.recv(1024).decode()  # 接受客户端发来的内容,长度,decode方法转化为字符串
        if data == 'exit':
            print("客户端发送完成, 断开连接。")
            break
        print("接收到客户端 %s 发送来的信息 : %s" % (client, data))
        res = data.upper()  # 处理,发来的字母转换为全大写
        # str
        link.sendall(res.encode())  # 应答,将字符串内容返回给客户端,encode转换为字节数组,字符串不能在网络上传输
    link.close()


hostaddress = ("127.0.0.1", 8888)   # 定义本机地址,端口,IP地址代表一台服务器,端口号代表运行在终端上的某一个应用程序

# 使用ipv4,使用TCP协议
sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # 创建一个socket对象
sk.bind(hostaddress)  # 绑定
sk.listen(5)    # 监听,默认是1
print("启动socket服务,等待客户端连接...")

while True:
    conn, clientaddress = sk.accept()  # 阻塞状态,等到客户端一个连接的对象
    xd = threading.Thread(target=deal, args=(conn, clientaddress))  # 创建一个线程,方法deal,参数 连接,客户端地址
    xd.start()  # 运行

可以处理并发场景,不同的客户端有不同的端口号

当连接的客户端断开,服务端也并不会停下来,其他的客户端还可以继续连接
在这里插入图片描述

实际写代码中,服务端比较复杂

7、socketserver

实例代码中起名字不能和包名一样,所以起名字可以加一个下划线以便于区分,避免写错

出现了一次错误,端口不能同时使用(上一个例子中的server和这次socketserver端口都是8888)

import socketserver

class MyHandler(socketserver.BaseRequestHandler):  # 定义一个类,里面是它的父类
    def handle(self):    # handle是重写父类的一个方法,作用是子进程里处理数据
        while True:
            data = self.request.recv(1024).decode()
            if data == 'exit':
                print("客户端发送完成, 断开连接。")
                break
            print("接收到客户端 %s 发送来的信息 : %s" % (self.client_address, data))  # 地址
            res = data.upper()  # 处理,发来的字母转换为全大写
            # str
            self.request.sendall(res.encode())  # 应答,将字符串内容返回给客户端,encode转换为字节数组,字符串不能在网络上传输
        self.request.close()


# 定义完,开始调用
hostaddress = ("127.0.0.1", 8888)
server = socketserver.ThreadingTCPServer(hostaddress, MyHandler)  # 传入地址参数,处理类
print("启动socket服务,等待客户端连接...")
server.serve_forever()   # 启动服务器的监听

8、UDP的server和client

上面的例子都是TCP的,此来介绍UDP的server和client

服务端

import socket

hostaddress = ('127.0.0.1', 8888)

sk = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)  # 第二个参数代表的是以UDP的方式创造监听
sk.bind(hostaddress)
print("启动udp socket服务,等待客户端数据...")
while True:
    data = sk.recv(1024).decode()
    print("udp服务器接收到客户端的数据: %s" % data)
    if data == 'exit':
        print("客户端请求退出。")
        break

sk.close()

客户端


import socket

serveraddress = ("127.0.0.1", 9999)
sk = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

while True:
    sss = input('[udp]发送内容:').strip()
    sk.sendto(sss.encode(), serveraddress) # sendto是udp方式的发送,不需要建立连接,直接往ip地址扔
    if sss == 'exit':
        print('客户端退出。')
        break

sk.close()
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
一个简单的socket网络编程例子: 服务器代码: #include #include #include #include #pragma comment(lib,"ws2_32.lib") //这句话的意思是加载ws2_32.lib这个静态库 #define NETWORK_EVENT WM_USER+100 //如果你用mfc做开发,你可以点击菜单project-〉setting-〉link-〉object/library中添加这个静态库。 //如果你用c语言,你需要通过#pragma comment(命令来连接静态库 int main(int argc, char* argv[]){ HANDLE hThread = NULL; //判断是否输入了端口号 if(argc!=3){ printf("Usage: %sPortNumber\n",argv[1]); exit(-1); } //把端口号转化成整数 short port; if((port = atoi(argv[2]))==0){ printf("端口号有误!"); exit(-1); } WSADATA wsa; //初始化套接字DLL if(WSAStartup(MAKEWORD(2,2),&wsa)!=0){ //高字节指定了次版本号,低字节指定了主版本号,两个字节加到一起,就是你想要的Winsock库的版本号了 printf("套接字初始化失败!"); exit(-1); } //创建套接字 SOCKET serverSocket; if((serverSocket=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP))==INVALID_SOCKET){ printf("创建套接字失败!"); exit(-1); } struct sockaddr_in serverAddress; memset(&serverAddress,0,sizeof(sockaddr_in)); serverAddress.sin_family=AF_INET; serverAddress.sin_addr.S_un.S_addr = htonl(INADDR_ANY); serverAddress.sin_port = htons(port); //绑定 if(bind(serverSocket,(sockaddr*)&serverAddress,sizeof(serverAddress))==SOCKET_ERROR){ printf("套接字绑定到端口失败!端口: %d\n",port); exit(-1); } //进入侦听状态 if(listen(serverSocket,SOMAXCONN)==SOCKET_ERROR){ printf("侦听失败!"); exit(-1); } printf("Server %d is listening......\n",port); SOCKET clientSocket[5],maxSocket;//用来和客户端通信的套接字 struct sockaddr_in clientAddress;//用来和客户端通信的套接字地址 memset(&clientAddress,0,sizeof(clientAddress)); int addrlen = sizeof(clientAddress); fd_set fd_read; int i=0; int j; char buf[4096]; char buff[4096]="exit"; while(1) { FD_ZERO(&fd_read); maxSocket=serverSocket; FD_SET(serverSocket,&fd_read); //FD_SET(clientSocket[i-1],&fd_read); for(j=0;j<i;j++) { FD_SET(clientSocket[j],&fd_read); if(maxSocket"); //gets(buff); if(select(maxSocket+1,&fd_read,NULL,NULL,NULL)>0) { if(FD_ISSET(serverSocket,&fd_read)) { if(buff=="") { if((clientSocket[i++]=accept(serverSocket,(sockaddr*)&clientAddress,&addrlen))==INVALID_SOCKET) { printf("接受客户端连接失败!"); exit(-1); } else { for(j=0;j5) { printf("超过最大客户端数"); exit(-1); } } else { int bytes; for(int k=0;k<i;k++) { if(FD_ISSET(clientSocket[k],&fd_read)) { bytes=recv(clientSocket[k],buf,sizeof(buf),0); if(bytes==-1) { //listen(serverSocket,SOMAXCONN); for (int l=k;l<i;l++) clientSocket[l]=clientSocket[l+1]; i--; } /*if(bytes==0) { //printf("fdsdf"); listen(serverSocket,SOMAXCONN); for (int l=k;l0) { buf[bytes]='\0'; printf("Message from %s: %s\n",inet_ntoa(clientAddress.sin_addr),buf); if(send(clientSocket[k],buf,bytes,0)==SOCKET_ERROR) { printf("发送数据失败!"); exit(-1); } } } } } } } //清理套接字占用的资源 WSACleanup(); return 0; } 客户端代码: #include #include #include #pragma comment(lib,"ws2_32.lib") int main(int argc, char* argv[]){ //判断是否输入了IP地址和端口号 if(argc!=4){ printf("Usage: %s IPAddress PortNumber\n",argv[1]); exit(-1); } //把字符串的IP地址转化为u_long unsigned long ip; if((ip=inet_addr(argv[2]))==INADDR_NONE){ printf("不合法的IP地址:%s",argv[1]); exit(-1); } //把端口号转化成整数 short port; if((port = atoi(argv[3]))==0){ printf("端口号有误!"); exit(-1); } printf("Connecting to %s:%d......\n",inet_ntoa(*(in_addr*)&ip),port); WSADATA wsa; //初始化套接字DLL if(WSAStartup(MAKEWORD(2,2),&wsa)!=0){ printf("套接字初始化失败!"); exit(-1); } //创建套接字 SOCKET sock,serverSocket; if((sock=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP))==INVALID_SOCKET){ printf("创建套接字失败!"); exit(-1); } struct sockaddr_in serverAddress; memset(&serverAddress,0,sizeof(sockaddr_in)); serverAddress.sin_family=AF_INET; serverAddress.sin_addr.S_un.S_addr = ip; serverAddress.sin_port = htons(port); //建立和服务器的连接 if(connect(sock,(sockaddr*)&serverAddress,sizeof(serverAddress))==SOCKET_ERROR) { printf("建立连接失败!"); exit(-1); } char buf[4096]; while(1){ printf(">"); //从控制台读取一行数据 gets(buf); if(send(sock,buf,strlen(buf),0)==SOCKET_ERROR){ printf("发送c数据失败!"); exit(-1); } int bytes; if((bytes=recv(sock,buf,sizeof(buf),0))==SOCKET_ERROR) { printf("接收c数据失败!\n"); exit(-1); } else { buf[bytes]='\0'; printf("Message from %s: %s\n",inet_ntoa(serverAddress.sin_addr),buf); } } //清理套接字占用的资源 WSACleanup(); return 0; }

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值