网络编程之socket编程

本文详细介绍了基于TCP和UDP协议的套接字编程,涵盖服务端与客户端的工作流程、通信循环及常用API,包括bind、listen、accept、connect等关键操作。

socket编程

​ Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。

​ 我们经常把Socket翻译为套接字,Socket是在应用层和传输层之间的一个抽象层,它不属于七层协议的任何一层,它把TCP/IP层复杂的操作抽象为几个简单的接口供应用层调用已实现进程在网络中通信。如下图示:

121-基于TCP协议的套接字编程-socket层.jpg?x-oss-process=style/watermark

套接字工作流程

​ 套接字的工作流程,就类似于我们现实生活中两人拨打电话,一人先拨号,然后等待,等另一人听到铃声后提起电话,这时两人建立了连接,就可以进行通话了,通话结束后,挂掉电话结束此次交流。流程图如下所示:

121-基于TCP协议的套接字编程-socket流程.jpg?x-oss-process=style/watermark

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

基于TCP协议的套接字编程

简单版本

​ 下面我们通过python来实现基于TCP协议的套接字编程。本次我们先实现服务端与客户端可以完成一次收发信息,详细代码如下:

**********************************服务端**************************************

# 1. 实例化对象socket
import socket

# 2. 得到对象
# 2.1. 如果不传参数,代表的是TCP协议,即默认为type = socket.SOCK_STREAM 代表TCP协议
# 2.2 type=socket.SOCK_DGRAM => UDP协议

server = socket.socket()
# 等价于server = socket.socket(type=socket.SOCK_STREAM)

# 3. 绑定
server.bind(('127.0.0.1', 8001))

# 4. 监听, 数字代表的是半连接池
server.listen(3)

print('服务端开始接收客户端消息了:')
# 5.sock、addr
# sock: 当前连接的客户端对象
# addr: 客户端的地址
sock, addr = server.accept()

# 6. 接收客户段发送的消息
# 1024代表的是字节数,接收数据的最大字节数
data = sock.recv(1024)
print('客户端数据:', data)

# 7. 给客户端返回数据
sock.send(data.upper())

# 8. 断开连接
sock.close()

# 9. 关闭服务端
server.close()


**********************************客户端**************************************

# 1. 实例化对象
import socket
client = socket.socket()

# 2. 连接
client.connect(('127.0.0.1', 8002))

# 3. 给服务端发送数据,发送的数据类型必须是字节类型
client.send('hello'.encode('utf-8'))

# 4. 接收服务端发送过来的数据
data = client.recv(1024)
print('服务端的数据:', data)

# 5. 断开连接
client.close()
加上链接循环

​ 上面的代码只能实现一次信息的收发,完成之后就会断开连接,那么我们来想想,我们难道只服务一个客户端吗?答案是否定的,那么我们如何实现服务多个客户端呢?就需要添加链接循环,这时就可以接收不同客户端的信息。代码如下:

**********************************服务端**************************************

import socket

# 1. 实例化对象socket
server = socket.socket()

# 2. 绑定
server.bind(('127.0.0.1', 8002))

# 3. 监听
server.listen(3)
print('服务端开始接收客户端消息了:')

while True:
    # 4. 阻塞
    sock, addr = server.accept()

    # 5. 接收客户段发送的消息
    data = sock.recv(1024) 
    print('客户端数据:', data)

    # 6. 给客户端返回数据
    sock.send(data.upper())

    # 7. 断开连接
    sock.close()

# 8. 关闭服务
server.close()


**********************************客户端**************************************
# 1. 实例化对象
import socket
client = socket.socket()


# 2. 连接
client.connect(('127.0.0.1', 8002))

# 3. 给服务端发送数据,发送的数据类型必须是字节类型
client.send('hello1111'.encode('utf-8'))

# 4. 接收服务端发送过来的数据
data = client.recv(1024)
print('服务端的数据:', data)

# 5. 断开连接
client.close()

加上通信循环

​ 上面的代码虽然实现了可以接收不同客户端通信,但是又有一个问题,一个客户端发送信息会一次性发完吗?答案是不一定的,每次建立链接发送的信息都是没有关联的,这样肯定是不行的,因此我们就需要对一个客户端服务完成后,再去服务另一个客户端,这就需要我们加上通信循环,实现代码如下:

**********************************服务端**************************************

# 1. 实例化对象socket
import socket

server = socket.socket()

# 2. 绑定
server.bind(('127.0.0.1', 8002))

# 3. 监听
server.listen(3)
print('服务端开始接收客户端消息了:')

while True:
	# 4. 阻塞
    sock, addr = server.accept()
    while True:
        try:
			# 5. 接收客户段发送的消息
            data = sock.recv(1024)  # hello
            if len(data) == 0:
                continue
            print('客户端数据:', data)

            # 6. 给客户端返回数据
            sock.send(data.upper()) # HELLO

        except Exception as e:
            print(e)
            break

    # 7. 断开连接
    sock.close()

# 8. 关闭服务
server.close()


**********************************客户端**************************************

# 1. 实例化对象
import socket
client = socket.socket()


# 2. 连接
client.connect(('127.0.0.1', 8002))

while True:
    input_data = input('请输入要发送的数据:')

    # 3. 给服务端发送数据,发送的数据类型必须是字节类型
    client.send(input_data.encode('utf-8'))

    # 4. 接收服务端发送过来的数据
    data = client.recv(1024)
    print('服务端的数据:', data)

# 5. 断开连接
client.close()

基于udp协议的套接字编程

​ 类似于上面的过程,只是遵循的协议不同而已,具体代码如下:

**********************************服务端**************************************
import socket

server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)  
server.bind(('127.0.0.1', 8080))

while True:
    data, client_addr = server.recvfrom(1024)
    print('===>', data, client_addr)
    server.sendto(data.upper(), client_addr)

server.close()

**********************************客户端**************************************
import socket

client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)  # 数据报协议-》UDP

while True:
    msg = input('>>: ').strip()  
    client.sendto(msg.encode('utf-8'), ('127.0.0.1', 8080))
    data, server_addr = client.recvfrom(1024)
    print(data)

client.close()

补充:

服务端套接字函数
方法用途
s.bind()绑定(主机,端口号)到套接字
s.listen()开始TCP监听
s.accept()被动接受TCP客户的连接,(阻塞式)等待连接的到来
客户端套接字函数
方法用途
s.connect()主动初始化TCP服务器连接
s.connect_ex()connect()函数的扩展版本,出错时返回出错码,而不是抛出异常
公共套接字函数
方法用途
s.recv()接收TCP数据
s.send()发送TCP数据(send在待发送数据量大于己端缓存区剩余空间时,数据丢失,不会发完)
s.sendall()发送完整的TCP数据(本质就是循环调用send,sendall在待发送数据量大于己端缓存区剩余空间时,数据不丢失,循环调用send直到发完)
s.recvfrom()接收UDP数据
s.sendto()发送UDP数据
s.getpeername()连接到当前套接字的远端的地址
s.getsockname()当前套接字的地址
s.getsockopt()返回指定套接字的参数
s.setsockopt()设置指定套接字的参数
s.close()关闭套接字
一个简单的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、付费专栏及课程。

余额充值