-
2018-07-16 00:02:05
网络编程
自从互联网诞生以来,现在基本上所有的程序都是网络程序,很少有单机版的程序了。计算机网络就是把各个计算机连接到一起,让网络中的计算机可以互相通信。网络编程就是如何在程序中实现两台计算机的通信。
网络编程对所有开发语言都是一样的,Python也不例外。用Python进行网络编程,就是在Python程序本身这个进程内,连接别的服务器进程的通信端口进行通信。
基本概念
IP地址实际上是一个32位整数(称为IPv4),以字符串表示的IP地址如192.168.0.1实际上是把32位整数按8位分组后的数字表示,目的是便于阅读。IPv6地址实际上是一个128位整数,它是目前使用的IPv4的升级版,以字符串表示类似于2001:0db8:85a3:0042:1000:8a2e:0370:7334。
TCP协议则是建立在IP协议之上的。TCP协议负责在两台计算机之间建立可靠连接,保证数据包按顺序到达。TCP协议会通过握手建立连接,然后,对每个IP包编号,确保对方按顺序收到,如果包丢掉了,就自动重发。
许多常用的更高级的协议都是建立在TCP协议基础上的,比如用于浏览器的HTTP协议、发送邮件的SMTP协议等。
一个TCP报文除了包含要传输的数据外,还包含源IP地址和目标IP地址,源端口和目标端口。
端口有什么作用?在两台计算机通信时,只发IP地址是不够的,因为同一台计算机上跑着多个网络程序。一个TCP报文来了之后,到底是交给浏览器还是QQ,就需要端口号来区分。每个网络程序都向操作系统申请唯一的端口号,这样,两个进程在两台计算机之间建立网络连接就需要各自的IP地址和各自的端口号。
一个进程也可能同时与多个计算机建立链接,因此它会申请很多端口。
TCP编程
什么是 Socket?
Socket又称”套接字”,应用程序通常通过”套接字”向网络发出请求或者应答网络请求,使主机间或者一台计算机上的进程间可以通讯。
socket()函数
Python中,我们用socket()函数来创建套接字。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.close() 关闭套接字
服务器
和客户端编程相比,服务器编程就要复杂一些。
服务器进程首先要绑定一个端口并监听来自其他客户端的连接。如果某个客户端连接过来了,服务器就与该客户端建立Socket连接,随后的通信就靠这个Socket连接了。
所以,服务器会打开固定端口(比如80)监听,每来一个客户端连接,就创建该Socket连接。由于服务器会有大量来自客户端的连接,所以,服务器要能够区分一个Socket连接是和哪个客户端绑定的。一个Socket依赖4项:服务器地址、服务器端口、客户端地址、客户端端口来唯一确定一个Socket。
但是服务器还需要同时响应多个客户端的请求,所以,每个连接都需要一个新的进程或者新的线程来处理,否则,服务器一次就只能服务一个客户端了。
服务器:import socket #服务器端 severSocket=socket.socket( family=socket.AF_INET, type=socket.SOCK_STREAM) severSocket.bind(("192.168.0.140",7772)) print("服务器等待。。。。") severSocket.listen(5) clienSocket,addr=severSocket.accept() print(clienSocket) print(addr) while True: msg=input("服务器输入:") clienSocket.send(msg.encode("utf-8")) msg=clienSocket.recv(1024).decode("utf-8") print(msg) clienSocket.close()
客户端:
import socket ClientSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) ClientSocket.connect((" 192.168.0.140", 8888)) print("客户端已经链接") msg = ClientSocket.recv(1024).decode("utf-8") print(msg) ClientSocket.send(b"beybey") ClientSocket.close()
更多相关内容 -
黑马_Linux网络编程-网络基础-socket编程-高并发服务器
2018-02-16 16:52:42黑马程序员linux服务器开发网络编程配套文档,很好的参考资料 -
网络游戏服务器编程
2016-12-26 13:27:26电子书下载 : ...本书的主要内容:网络的基本原理、UNIX套接字编辑、Winsock编程、游戏服务器编程、游戏服务器编程开发模型、用于插件式游戏的基本模块的开发、网络程序库。 -
Linux网络编程-网络基础-socket编程-高并发服务器
2018-04-11 15:29:37Linux网络编程-网络基础-socket编程-高并发服务器,非常详细的资料,值得你学习。 -
C语言之网络编程(服务器和客户端)
2017-08-18 20:27:36Linux网络编程 1、 套接字:源IP地址和目的IP地址以及源端口号和目的端口号的组合称为套接字。其用于标识客户端请求的服务器和服务。 常用的TCP/IP协议的3种套接字类型如下所示。 (1)流套接字(SOCK_STREAM): ...Linux网络编程
1、 套接字:源IP地址和目的IP地址以及源端口号和目的端口号的组合称为套接字。其用于标识客户端请求的服务器和服务。
常用的TCP/IP协议的3种套接字类型如下所示。
(1)流套接字(SOCK_STREAM):
流套接字用于提供面向连接、可靠的数据传输服务。该服务将保证数据能够实现无差错、无重复发送,并按顺序接收。流套接字之所以能够实现可靠的数据服务,原因在于其使用了传输控制协议,即TCP(The Transmission ControlProtocol)协议。
(2) 数据报套接字(SOCK_DGRAM):
数据报套接字提供了一种无连接的服务。该服务并不能保证数据传输的可靠性,数据有可能在传输过程中丢失或出现数据重复,且无法保证顺序地接收到数据。数据报套接字使用UDP(User Datagram Protocol)协议进行数据的传输。由于数据报套接字不能保证数据传输的可靠性,对于有可能出现的数据丢失情况,需要在程序中做相应的处理。
(3) 原始套接字(SOCK_RAW):(一般不用这个套接字)
原始套接字(SOCKET_RAW)允许对较低层次的协议直接访问,比如IP、 ICMP协议,它常用于检验新的协议实现,或者访问现有服务中配置的新设备,因为RAW SOCKET可以自如地控制Windows下的多种协议,能够对网络底层的传输机制进行控制,所以可以应用原始套接字来操纵网络层和传输层应用。比如,我们可以通过RAW SOCKET来接收发向本机的ICMP、IGMP协议包,或者接收TCP/IP栈不能够处理的IP包,也可以用来发送一些自定包头或自定协议的IP包。网络监听技术很大程度上依赖于SOCKET_RAW
2、 套接字基本函数:
(1) 创建套接字:int socket(int family, int type, intprotocol);
功能介绍:
在Linux操作系统中,一切皆文件,这个大家都知道,个人理解创建socket的过程其实就是一个获得文件描述符的过程,当然这个过程会是比较复杂的。可以从内核中找到创建socket的代码,并且socket的创建和其他的listen,bind等操作分离开来。socket函数完成正确的操作是返回值大于0的文件描述符,当返回小于0的值时,操作错误。同样是返回一个文件描述符,但是会因为三个参数组合不同,对于数据具体的工作流程不同,对于应用层编程来说,这些也是不可见的。
参数说明:
从socket创建的函数可以看出,socket有三个参数,family代表一个协议族,比较熟知的就是AF_INET,PF_PACKET等;第二个参数是协议类型,常见类型是SOCK_STREAM,SOCK_DGRAM, SOCK_RAW, SOCK_PACKET等;第三个参数是具体的协议,对于标准套接字来说,其值是0,对于原始套接字来说就是具体的协议值。
(2) 套接字绑定函数: intbind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);
功能介绍:
bind函数主要应用于服务器模式一端,其主要的功能是将addrlen长度 structsockaddr类型的myaddr地址与sockfd文件描述符绑定到一起,在sockaddr中主要包含服务器端的协议族类型,网络地址和端口号等。在客户端模式中不需要使用bind函数。当bind函数返回0时,为正确绑定,返回-1,则为绑定失败。
参数说明:
bind函数的第一个参数sockfd是在创建socket套接字时返回的文件描述符。
bind函数的第二个参数是structsockaddr类型的数据结构,由于structsockaddr数据结构类型不方便设置,所以通常会通过对tructsockaddr_in进行地质结构设置,然后进行强制类型转换成structsockaddr类型的数据,
(3) 监听函数:int listen(int sockfd, int backlog);
功能介绍:
刚开始理解listen函数会有一个误区,就是认为其操作是在等在一个新的connect的到来,其实不是这样的,真正等待connect的是accept操作,listen的操作就是当有较多的client发起connect时,server端不能及时的处理已经建立的连接,这时就会将connect连接放在等待队列中缓存起来。这个等待队列的长度有listen中的backlog参数来设定。listen和accept函数是服务器模式特有的函数,客户端不需要这个函数。当listen运行成功时,返回0;运行失败时,返回值位-1.
参数说明:
sockfd是前面socket创建的文件描述符;backlog是指server端可以缓存连接的最大个数,也就是等待队列的长度。
(4) 请求接收函数: int accept(int sockfd, structsockaddr *client_addr, socklen_t *len);
功能介绍:
接受函数accept其实并不是真正的接受,而是客户端向服务器端监听端口发起的连接。对于TCP来说,accept从阻塞状态返回的时候,已经完成了三次握手的操作。Accept其实是取了一个已经处于connected状态的连接,然后把对方的协议族,网络地址以及端口都存在了client_addr中,返回一个用于操作的新的文件描述符,该文件描述符表示客户端与服务器端的连接,通过对该文件描述符操作,可以向client端发送和接收数据。同时之前socket创建的sockfd,则继续监听有没有新的连接到达本地端口。返回大于0的文件描述符则表示accept成功,否则失败。
参数说明:
sockfd是socket创建的文件描述符;client_addr是本地服务器端的一个structsockaddr类型的变量,用于存放新连接的协议族,网络地址以及端口号等;第三个参数len是第二个参数所指内容的长度,对于TCP来说其值可以用sizeof(structsockaddr_in)来计算大小,说要说明的是accept的第三个参数要是指针的形式,因为这个值是要传给协议栈使用的。
(5)客户端请求连接函数: intconnect(int sock_fd, struct sockaddr *serv_addr,int addrlen);
功能介绍:
连接函数connect是属于client端的操作函数,其目的是向服务器端发送连接请求,这也是从客户端发起TCP三次握手请求的开始,服务器端的协议族,网络地址以及端口都会填充到connect函数的serv_addr地址当中。当connect返回0时说明已经connect成功,返回值是-1时,表示connect失败。
参数说明:
connect的第一个参数是socket创建的文件描述符;第二个参数是一个structsockaddr类型的指针,这个参数中设置的是要连接的目标服务器的协议族,网络地址以及端口号;第三个参数表示第二个参数内容的大小,与accept不同,这个值不是一个指针。
在服务器端和客户端建立连接之后是进行数据间的发送和接收,主要使用的接收函数是recv和read,发送函数是send和write。因为对于socket套接字来说,最终实际操作的是文件描述符,所以可以使用对文件进行操作的接收和发送函数对socket套接字进行操作。read和write函数是文件编程里的知识,所以这里不再做多与的赘述。
3、 有了以上的知识,那么我们就可以编写一个简单的服务器和客户端了
(1) 简易服务器:这个服务器只能与一个客户端相连接,如果有多个客户端就不能用这个服务器进行连接。
代码: #include <sys/types.h> #include <sys/socket.h> #include <stdio.h> #include <unistd.h> #include <string.h> #include <arpa/inet.h> #define PORT 9990 //端口号 #define SIZE 1024 //定义的数组大小 int Creat_socket() //创建套接字和初始化以及监听函数 { int listen_socket = socket(AF_INET, SOCK_STREAM, 0); //创建一个负责监听的套接字 if(listen_socket == -1) { perror("socket"); return -1; } struct sockaddr_in addr; memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; /* Internet地址族 */ addr.sin_port = htons(PORT); /* 端口号 */ addr.sin_addr.s_addr = htonl(INADDR_ANY); /* IP地址 */ int ret = bind(listen_socket, (struct sockaddr *)&addr, sizeof(addr)); //连接 if(ret == -1) { perror("bind"); return -1; } ret = listen(listen_socket, 5); //监听 if(ret == -1) { perror("listen"); return -1; } return listen_socket; } int wait_client(int listen_socket) { struct sockaddr_in cliaddr; int addrlen = sizeof(cliaddr); printf("等待客户端连接。。。。\n"); int client_socket = accept(listen_socket, (struct sockaddr *)&cliaddr, &addrlen); //创建一个和客户端交流的套接字 if(client_socket == -1) { perror("accept"); return -1; } printf("成功接收到一个客户端:%s\n", inet_ntoa(cliaddr.sin_addr)); return client_socket; } void hanld_client(int listen_socket, int client_socket) //信息处理函数,功能是将客户端传过来的小写字母转化为大写字母 { char buf[SIZE]; while(1) { int ret = read(client_socket, buf, SIZE-1); if(ret == -1) { perror("read"); break; } if(ret == 0) { break; } buf[ret] = '\0'; int i; for(i = 0; i < ret; i++) { buf[i] = buf[i] + 'A' - 'a'; } printf("%s\n", buf); write(client_socket, buf, ret); if(strncmp(buf, "end", 3) == 0) { break; } } close(client_socket); } int main() { int listen_socket = Creat_socket(); int client_socket = wait_client(listen_socket); hanld_client(listen_socket, client_socket); close(listen_socket); return 0; }
(2) 多进程并发服务器:该服务器就完全弥补了上一个服务器的不足,可以同时处理多个客户端,只要有客户端来连接它,他就能响应。在我们这个服务器中,父进程主要负责监听,所以在父进程一开始就要把父进程的接收函数关闭掉,防止父进程在接收函数处阻塞,导致子进程不能创建成功。同理,子进程主要负责接收客户端,并做相关处理,所以子进程在一创建就要把监听函数关闭,不然会导致服务器功能的紊乱。这个服务器有一个特别要注意的是,子进程在退出时会产生僵尸进程,所以我们一定要对子进程退出后进行处理。
代码: #include <sys/types.h> #include <sys/socket.h> #include <stdio.h> #include <unistd.h> #include <string.h> #include <arpa/inet.h> #include <signal.h> #include <sys/wait.h> #define PORT 9990 #define SIZE 1024 int Creat_socket() //创建套接字和初始化以及监听函数 { int listen_socket = socket(AF_INET, SOCK_STREAM, 0); //创建一个负责监听的套接字 if(listen_socket == -1) { perror("socket"); return -1; } struct sockaddr_in addr; memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; /* Internet地址族 */ addr.sin_port = htons(PORT); /* 端口号 */ addr.sin_addr.s_addr = htonl(INADDR_ANY); /* IP地址 */ int ret = bind(listen_socket, (struct sockaddr *)&addr, sizeof(addr)); //连接 if(ret == -1) { perror("bind"); return -1; } ret = listen(listen_socket, 5); //监听 if(ret == -1) { perror("listen"); return -1; } return listen_socket; } int wait_client(int listen_socket) { struct sockaddr_in cliaddr; int addrlen = sizeof(cliaddr); printf("等待客户端连接。。。。\n"); int client_socket = accept(listen_socket, (struct sockaddr *)&cliaddr, &addrlen); //创建一个和客户端交流的套接字 if(client_socket == -1) { perror("accept"); return -1; } printf("成功接收到一个客户端:%s\n", inet_ntoa(cliaddr.sin_addr)); return client_socket; } void hanld_client(int listen_socket, int client_socket) //信息处理函数,功能是将客户端传过来的小写字母转化为大写字母 { char buf[SIZE]; while(1) { int ret = read(client_socket, buf, SIZE-1); if(ret == -1) { perror("read"); break; } if(ret == 0) { break; } buf[ret] = '\0'; int i; for(i = 0; i < ret; i++) { buf[i] = buf[i] + 'A' - 'a'; } printf("%s\n", buf); write(client_socket, buf, ret); if(strncmp(buf, "end", 3) == 0) { break; } } close(client_socket); } void handler(int sig) { while (waitpid(-1, NULL, WNOHANG) > 0) { printf ("成功处理一个子进程的退出\n"); } } int main() { int listen_socket = Creat_socket(); signal(SIGCHLD, handler); //处理子进程,防止僵尸进程的产生 while(1) { int client_socket = wait_client(listen_socket); //多进程服务器,可以创建子进程来处理,父进程负责监听。 int pid = fork(); if(pid == -1) { perror("fork"); break; } if(pid > 0) { close(client_socket); continue; } if(pid == 0) { close(listen_socket); hanld_client(listen_socket, client_socket); break; } } close(listen_socket); return 0; }
(3) 多线程并发服务器:上一个多进程服务器有一个缺点,就是每当一个子进程得到响应的时候,都要复制父进程的一切信息,这样就导致了CPU资源的浪费,当客户端有很多来连接这个服务器的时候,就会产生很多的子进程,会导致服务器的响应变得很慢。所以我们就想到了多线程并发服务器,我们知道线程的速度是进程的30倍左右,所以我们就用线程来做服务器。
代码: #include <sys/types.h> #include <sys/socket.h> #include <stdio.h> #include <unistd.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> #define PORT 9990 #define SIZE 1024 int Creat_socket() //创建套接字和初始化以及监听函数 { int listen_socket = socket(AF_INET, SOCK_STREAM, 0); //创建一个负责监听的套接字 if(listen_socket == -1) { perror("socket"); return -1; } struct sockaddr_in addr; memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; /* Internet地址族 */ addr.sin_port = htons(PORT); /* 端口号 */ addr.sin_addr.s_addr = htonl(INADDR_ANY); /* IP地址 */ int ret = bind(listen_socket, (struct sockaddr *)&addr, sizeof(addr)); //连接 if(ret == -1) { perror("bind"); return -1; } ret = listen(listen_socket, 5); //监听 if(ret == -1) { perror("listen"); return -1; } return listen_socket; } int wait_client(int listen_socket) { struct sockaddr_in cliaddr; int addrlen = sizeof(cliaddr); printf("等待客户端连接。。。。\n"); int client_socket = accept(listen_socket, (struct sockaddr *)&cliaddr, &addrlen); //创建一个和客户端交流的套接字 if(client_socket == -1) { perror("accept"); return -1; } printf("成功接收到一个客户端:%s\n", inet_ntoa(cliaddr.sin_addr)); return client_socket; } void hanld_client(int listen_socket, int client_socket) //信息处理函数,功能是将客户端传过来的小写字母转化为大写字母 { char buf[SIZE]; while(1) { int ret = read(client_socket, buf, SIZE-1); if(ret == -1) { perror("read"); break; } if(ret == 0) { break; } buf[ret] = '\0'; int i; for(i = 0; i < ret; i++) { buf[i] = buf[i] + 'A' - 'a'; } printf("%s\n", buf); write(client_socket, buf, ret); if(strncmp(buf, "end", 3) == 0) { break; } } close(client_socket); } int main() { int listen_socket = Creat_socket(); while(1) { int client_socket = wait_client(listen_socket); pthread_t id; pthread_create(&id, NULL, hanld_client, (void *)client_socket); //创建一个线程,来处理客户端。 pthread_detach(id); //把线程分离出去。 } close(listen_socket); return 0; }
代码: #include <sys/types.h> #include <sys/socket.h> #include <stdio.h> #include <unistd.h> #include <string.h> #include <arpa/inet.h> #define PORT 9990 #define SIZE 1024 int main() { int client_socket = socket(AF_INET, SOCK_STREAM, 0); //创建和服务器连接套接字 if(client_socket == -1) { perror("socket"); return -1; } struct sockaddr_in addr; memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; /* Internet地址族 */ addr.sin_port = htons(PORT); /* 端口号 */ addr.sin_addr.s_addr = htonl(INADDR_ANY); /* IP地址 */ inet_aton("127.0.0.1", &(addr.sin_addr)); int addrlen = sizeof(addr); int listen_socket = connect(client_socket, (struct sockaddr *)&addr, addrlen); //连接服务器 if(listen_socket == -1) { perror("connect"); return -1; } printf("成功连接到一个服务器\n"); char buf[SIZE] = {0}; while(1) //向服务器发送数据,并接收服务器转换后的大写字母 { printf("请输入你相输入的:"); scanf("%s", buf); write(client_socket, buf, strlen(buf)); int ret = read(client_socket, buf, strlen(buf)); printf("buf = %s", buf); printf("\n"); if(strncmp(buf, "END", 3) == 0) //当输入END时客户端退出 { break; } } close(listen_socket); return 0; }
-
C++_面试题(服务器编程、网络编程)
2013-07-18 10:30:14C++_面试题(服务器编程、网络编程),很全,很实用! -
《TCP IP网络编程》.pdf
2018-03-06 11:10:05为初学者准备的网络编程! 韩国TCP/IP经典教程!手把手教你套接字编程! 本书涵盖操作系统、系统...本书针对网络编程初学者,面向具备C语言基础的套接字网络编程学习者,适合所有希望学习Linux和Windows网络编程的人。 -
Linux服务器高性能编程
2017-09-07 10:43:57现在internet(因特网)使用的主流协议族是TCP/IP协议族,它是一个分层、多协议的通信体系。本章简要讨论TCP/IP协议族各层包含的主要协议,以及它们之间是如何协作完成网络通信的。 -
《TCP IP网络编程》
2015-06-02 22:23:10《TCP/IP网络编程》针对网络编程初学者,面向具备C 语言基础的套接字网络编程学习者,适合所有希望学习Linux和Windows 网络编程的人。 第一部分主要介绍网络编程基础知识。此部分主要论述Windows和Linux平台网络编程... -
linux网络编程--socket服务器和客户端TCP编程及多进程编程
2021-11-12 16:49:01迭代服务器编程实现 2.1.1. 命令行参数解析 2.1.2. 创建服务器 socket 2.1.3. bind 绑定端口和ip 并且 开启listen 2.1.4. 开启accept 2.1.5. 通过文件IO系统调用对客户端进行读写 2.2. 客户端编程实现 2.2.1 客户端...文章目录
1.网络编程中客户端与服务器通信基本流程
2. 服务器和客户端编程实现<迭代服务器>
2.1. 迭代服务器编程实现
2.1.1. 命令行参数解析
- 服务器参数只有端口号,增加一个帮助参数<-h>,对该命令的用法进行说明
- 代码编写如下
void print_usage(char *progname) { printf("%s usage: \n", progname); printf("-p(--port): sepcify server port.\n"); printf("-h(--Help): print this help information.\n"); return ; } { int port = 0; int ch; struct option opts[] = { {"port", required_argument, NULL, 'p'}, {"help", no_argument, NULL, 'h'}, {NULL, 0, NULL, 0} }; while( (ch=getopt_long(argc, argv, "p:h", opts, NULL)) != -1 ) { switch(ch) { case 'p': port=atoi(optarg);//字符串 ----> 整形 break; case 'h': print_usage(argv[0]); return 0; } } if(!port) //未正常使用命令 { print_usage(argv[0]); return 0; } }
2.1.2. 创建服务器 socket
{ socket_fd = socket(AF_INET, SOCK_STREAM, 0);//IPV4 TCP if(socket_fd < 0) { printf("Create socket failure : %s\n", strerror(errno)); return -1; } printf("Create socket [%d] successful!\n", socket_fd); setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));//防止程序端口重用报错 }
2.1.3. bind 绑定端口和ip 并且 开启listen
{ memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(port); // 主机字节序 ----> 网络字节序 servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 监听所有的 IP 主机-----> 网络 rv = bind(socket_fd, (struct sockaddr *)&servaddr, sizeof(servaddr)); if(rv < 0) { printf("Socket[%d] bind on port [%d] failure : %s\n",socket_fd,port,strerror(errno)); return -2; } printf("Socket[%d] bind on port [%d] successful!\n", socket_fd, port); //listen listen(socket_fd, 13); }
2.1.4. 开启accept
- accept是一个阻塞函数,当 没有客户端连接服务器的时候,该程序会一直阻塞不返回,直到有一个客户端连接过来为止。当客户端调用connect函数就会触发服务器的accept函数返回,此时TCP连接就建立好了。
- 代码实现
//accept clifd = accept(socket_fd, (struct sockaddr *)&cliaddr, &len); if(clifd < 0) { printf("Accept new client failure:%s\n", strerror(errno)); continue; } printf("Accept new client [%s:%d] successfully!\n" ,inet_ntoa(cliaddr.sin_addr),ntohs(cliaddr.sin_port));//客户端的IP和端口
2.1.5. 通过文件IO系统调用对客户端进行读写
- 读客户端编程实现,使用read系统调用
{ memset(buf, 0, sizeof(buf)); rv = read(clifd, buf, sizeof(buf)); if(rv < 0) { printf("Read from client by clifd[%d] failure: %s\n", clifd , strerror(errno)); close(clifd); continue; } else if(rv == 0) { printf("Clifd[%d] get disconnected\n", clifd); close(clifd); continue; } else if(rv > 0) { printf("Read [%d] byte data from client clifd[%d] : %s\n", rv, clifd, buf); } }
- 对客户端进行write写消息,注意写完后不想继续通讯,需要关闭客户端描述符。
{ rv = write(clifd, MSG_STR, strlen(MSG_STR)); if(rv < 0) { printf("write to client by clifd[%d] failure: %s\n", clifd , strerror(errno)); close(clifd); continue; } printf("Close client fd[%d]\n", clifd); close(clifd); }
- 服务器关闭前也需要把监听前创建的socket描述符关闭。
2.2. 客户端编程实现
2.2.1 客户端命令行参数解析(带域名解析功能)
- 客户端参数有 服务器ip地址和服务器端口,增加帮助命令<-h>,增加域名命令<-d>,该代码仅实现域名的解析并打印其ip地址,由于没有公网IP无法进行测试,测试baidu.com可以解析出其IP地址
{ int ch; struct option opts[] = { {"ipaddr", required_argument, NULL, 'i'}, {"port", required_argument, NULL, 'p'}, {"domain_name", required_argument, NULL, 'd'}, {"help", no_argument, NULL, 'h'}, {NULL, 0, NULL, 0} }; while( (ch=getopt_long(argc, argv, "i:p:d:h", opts, NULL)) != -1 ) { switch(ch) { case 'i': servip=optarg; break; case 'p': port=atoi(optarg); break; case 'd': ser_domain_name=optarg; break; case 'h': print_usage(argv[0]); return 0; } } if( !servip || !port || !ser_domain_name) { print_usage(argv[0]); return 0; } //打印域名和解析后的IP地址 p = gethostbyname(ser_domain_name); printf("domain_name : %s servip : %s\n", ser_domain_name, inet_ntoa(*((struct in_addr *)p->h_addr))); }
2.2.2. 创建客户端socket
- 使用IPV4和TCP通讯
{ socket_fd = socket(AF_INET, SOCK_STREAM, 0); if(socket_fd < 0) { printf("Create socket failure :%s\n", strerror(errno)); return -1; } printf("Create socket [%d] successful!\n", socket_fd); }
2.2.3. 与服务器进行连接connect
- 把参数通过规定进行传入,注意字节序和字符串转换
{ memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(port);// 主----> 网 inet_aton(servip, &servaddr.sin_addr); //字符串 -----> 网络字节序 rv = connect(socket_fd, (struct sockaddr *)&servaddr, sizeof(servaddr)); if(rv < 0) { printf("Connect to server [%s:%d] failure : %s\n", servip, port, strerror(errno)); return -2; } printf("Connect to server [%s:%d] successful!\n", servip, port); }
2.2.4. 通过文件IO系统调用对服务器进行读写
- 和服务器一样,使用read和write进行消息的读写
- 注意客户端与服务器结束通讯后,不想继续通讯便可关闭socket描述符
{ rv = write(socket_fd, MSG_STR, strlen(MSG_STR)); if(rv < 0) { printf("Write to server by socket_fd [%d] failure : %s\n", socket_fd, strerror(errno)); break; } //read memset(buf, 0, sizeof(buf)); rv = read(socket_fd, buf, sizeof(buf)); if(rv < 0) { printf("Read from server by sockfd[%d] failure: %s\n", socket_fd , strerror(errno)); break; } else if(0 == rv) { printf("Socketfd[%d] get disconnected\n", socket_fd); break; } else if(rv > 0) { printf("Read [%d] byte from server socket_fd [%d] : %s\n", rv, socket_fd, buf); } close(socket_fd); }
3. 服务器+多进程编程
3.1. 简单介绍多进程编程
- 迭代服务器一次只能与一个客户端进行通讯,而实际中会有大量和客户端与服务器进行访问通讯,此时迭代服务器便不再适用。
- 使用多进程编程实现并发服务器,结构框图如下
3.2 编程实现
- 多进程编程实现,在原有的服务器代码上进行修改,在接收到客户端连接请求后,开启子进程与客户端进行通讯
{ pid = fork(); if(pid < 0) { printf("fork() create child process failure : %s\n", strerror(errno)); close(cli_fd); } else if(pid > 0) // 父进程 的功能函数 { close(cli_fd); //父进程 不需要 客户端描述符 continue; } else if(0 == pid) { close(socket_fd); //子进程不需要 该fd,该fd用于监听,监听到了后,使用新的fd ....//服务器与客户端通讯部分 } }
-
linux C语言 网络编程教程及源码
2017-05-12 13:06:092、Linux网络编程02——无连接和面向连接的区别 3、Linux网络编程03——字节序和地址转换 4、Linux网络编程04——套接字 5、Linux网络编程05——C/S与B/S架构的区别 6、Linux网络编程06——UDP协议编程 7、Linux网络... -
Linux网络编程 第2版(带详细目录)
2019-06-16 19:15:55第2篇介绍TCP/IP协议族简介、应用层网络服务程序简介、TCP网络编程基础、服务器和客户端信息的获取、数据的IO和复用、基于UDP协议的接收和发送、高级套接字、套接字选项、原始套接字、服务器模型选择,以及IPv6的... -
linux 高性能服务器编程
2018-11-25 15:40:20Linux网络编程 TCP/IP基础知识 涵盖socket epoll 多线程 -
Qt实现Winsock网络编程—Tcp服务端和客户端通信(多线程)示例程序demo
2018-11-06 21:21:10Qt实现Winsock网络编程—Tcp服务端和客户端通信(多线程)示例程序demo https://blog.csdn.net/qq_29542611/article/details/83778389 -
网络编程(二)TCP多线程服务器编程详解
2022-03-28 19:59:58前面已经介绍了单线程服务器编程的一个例子,为了实现一个服务器能够并发响应多个客户端的请求,这里引入多线程的方法: 将原来的单线程服务器改造成多线程服务器只需要改动下面两个地方 1,因为listen()监听函数...1,前文须知
上一篇文章:
网络编程(一)TCP单进程服务器编程详解
而这一篇主要介绍多线程服务器编程
注:这里多线程编程使用的是c++11标准里面的跨平台方法。我前面的博客也详细介绍了这种多线程编程方法的学习,这里就不加赘述,只介绍多线程服务器这个使用场景。
C++新特性(六)多线程(1)线程启动、结束,创建线程、join,detach,线程传参详解2,开始编程
前面已经介绍了单线程服务器编程的一个例子,为了实现一个服务器能够并发响应多个客户端的请求,这里引入多线程的方法:
将原来的单线程服务器改造成多线程服务器只需要改动下面两个地方
1,因为listen()监听函数过后,服务器的ip与端口就会暴露在网络中,网络中连接的各个客户端就可以连接该服务器,而所有的连接请求都会存储在监听文件描述符对应的读缓冲区中,每执行一次accept,就会从该监听文件描述符对应的读缓冲区中读取一个连接,因此,如果是多线程服务器,应该在主线程中将accept函数包含在一个while(1)循环中,让主线程不断从该缓冲区中接收连接。
2,当accept函数执行完以后,就要有对应的子线程处理accept函数返回的客户端,因此,在while循环内部,每当执行完accept成功以后,就创建一个子线程,让该线程去处理该客户端。并且注意子线程创建完以后,让他与主线程detach()。子线程内部的流程就是与客户端互相交流的一些代码。
具体一个例子如下:// server.c #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <arpa/inet.h> #include <thread> using namespace std; void deal_w_r(struct sockaddr_in cliaddr,int cfd) { // 打印客户端的地址信息 char ip[24] = {0}; printf("客户端的IP地址: %s, 端口: %d\n", inet_ntop(AF_INET, &cliaddr.sin_addr.s_addr, ip, sizeof(ip)), ntohs(cliaddr.sin_port)); // 5. 和客户端通信 while(1) { // 接收数据 char buf[1024]; memset(buf, 0, sizeof(buf)); int len = read(cfd, buf, sizeof(buf)); if(len > 0) { printf("%d客户端say: %s\n",ntohs(cliaddr.sin_port), buf); write(cfd, buf, len); } else if(len == 0) { printf("%d客户端断开了连接...\n",ntohs(cliaddr.sin_port)); break; } else { perror("read"); break; } } close(cfd); } int main() { // 1. 创建监听的套接字 int lfd = socket(AF_INET, SOCK_STREAM, 0); if(lfd == -1) { perror("socket"); exit(0); } // 2. 将socket()返回值和本地的IP端口绑定到一起 struct sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = htons(5000); // 大端端口 // INADDR_ANY代表本机的所有IP, 假设有三个网卡就有三个IP地址 // 这个宏可以代表任意一个IP地址 // 这个宏一般用于本地的绑定操作 addr.sin_addr.s_addr = INADDR_ANY; // 这个宏的值为0 == 0.0.0.0 // inet_pton(AF_INET, "192.168.237.131", &addr.sin_addr.s_addr); int ret = bind(lfd, (struct sockaddr*)&addr, sizeof(addr)); if(ret == -1) { perror("bind"); exit(0); } // 3. 设置监听 ret = listen(lfd, 128); if(ret == -1) { perror("listen"); exit(0); } // 4. 阻塞等待并接受客户端连接 struct sockaddr_in cliaddr; int clilen = sizeof(cliaddr); while(1) { int cfd = accept(lfd, (struct sockaddr*)&cliaddr, (socklen_t*)&clilen); if(cfd == -1) { perror("accept"); exit(0); } thread obj(deal_w_r,cliaddr,cfd); obj.detach(); } close(lfd); return 0; }
注意:在vscode中有可能识别不了c++11标准里面的thread,对于thread可能会标红线,报编译错误,因此需要点击该红线,编辑"includePath”设置,设置c的标准为c11,c++标准为c++11。并且编译的时候记得加入参数
-std=c++11
-lpthreadg++ server.cpp -std=c++11 -lpthread -o s
tcp客户端:
// client.c #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <arpa/inet.h> int main() { // 1. 创建通信的套接字 int fd = socket(AF_INET, SOCK_STREAM, 0); if(fd == -1) { perror("socket"); exit(0); } // 2. 连接服务器 struct sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = htons(5000); // 大端端口 inet_pton(AF_INET, "39.108.179.82", &addr.sin_addr.s_addr); int ret = connect(fd, (struct sockaddr*)&addr, sizeof(addr)); if(ret == -1) { perror("connect"); exit(0); } // 3. 和服务器端通信 int number = 0; while(1) { // 发送数据 char buf[1024]; sprintf(buf, "你好, 服务器...%d\n", number++); write(fd, buf, strlen(buf)+1); // 接收数据 memset(buf, 0, sizeof(buf)); int len = read(fd, buf, sizeof(buf)); if(len > 0) { printf("服务器say: %s\n", buf); } else if(len == 0) { printf("服务器断开了连接...\n"); break; } else { perror("read"); break; } sleep(1); // 每隔1s发送一条数据 } close(fd); return 0; }
除了使用多线程方法可以让服务器处理多个客户端的请求以外,io多路转接的方法也可以让一个服务器为多个客户端服务,io多路转接的方法有select,poll,epoll,下一篇首先介绍一下select。
下一篇:
网络编程(三)TCP IO多路转接服务器编程(select) -
Java网络编程之TCP协议下—上传文件到服务器程序
2012-03-09 21:38:09Java网络编程之TCP协议下—上传文件到服务器程序,欢迎大家下载和知道 -
IOS网络编程与云端应用最佳实践,完整扫描版
2014-10-11 21:57:18《清华开发者书库:iOS网络编程与云端应用最佳实践》是介绍iOS 6网络编程和云端应用开发技术书籍,介绍了苹果网络、数据交换格式、Web Service、iCloud、定位服务、地图、推送通知、Newsstand、应用内购买、Passbook... -
Linux高性能服务器编程PDF带目录高清版
2019-03-15 11:09:49《Linux高性能服务器编程》是Linux服务器编程领域的经典著作,资深Linux软件开发工程师撰写,从网络协议、服务器编程核心要素、原理机制、工具框架等多角度全面阐述编写高性能Linux服务器应用的方法、技巧和思想,... -
Python网络编程之socket编程(一)--使用TCP和UDP客户端和服务器通信
2016-07-30 09:43:43本文用python进行socket编程,实现客户端和服务器互相发送字符串,并在标准输出打印。 下面是客户端程序: #!/usr/bin/python import socket HOST = 'localhost' PORT = 6666 s=socket.socket(socket.AF_INET, ... -
计算机网络---网络编程套接字(二)
2022-04-12 14:30:33✨✨我和大家一样都是热爱编程✨,很高兴能在此和大家分享知识,希望在分享知识的同时,能和大家一起共同进步,取得好成绩,今天大家进入网络编程的新章节,如果有错误❌,欢迎指正哟,咋们废话不多说,跟紧步伐,开始学习吧~ ... -
【网络编程】python网络编程多线程实现
2022-04-05 10:37:12python threading在网络编程中的实际应用 -
c++教程网的linux网络编程视频下载
2017-12-20 15:45:09Linux网络编程(总共41集) 讲解Linux网络编程知识,分以下四个篇章。 Linux网络编程之TCP/IP基础篇 Linux网络编程之socket编程篇 Linux网络编程之进程间通信篇 Linux网络编程之线程篇 Linux网络编程之TCP/IP... -
计算机网络---网络编程套接字(一)
2022-04-10 19:52:43文章目录Socket套接字概念分类UDP数据报套接字编程Java中UDP套接字编程步骤DatagramSocket APIDatagramSocket 的构造方法:DatagramSocket 的常用方法:DatagramPacket APIDatagramPacket 的构造方法DatagramPacket ... -
MFC socket编程,网络传输(服务器端,客户端)
2012-09-08 10:13:46用socket套接字实现了文件传输,尤其适用于初学者学习socket进行创建,绑定,监听,接收等过程,服务器端和客户端进行通信的整个流程,MFC界面简单。发送端有,create ,listen,accept,send等函数;接收端有create... -
MFC:Socket编程—TCP服务端和多个客户端通信 示例代码
2019-01-12 20:57:19https://blog.csdn.net/qq_29542611/article/details/86371353 MFC:Socket编程—TCP服务端和多个客户端通信 示例代码 -
JAVA网络编程-TCP客户端与服务器端连接
2018-03-04 11:14:55在JAVA网络编程中,分客户端与服务器端,客户端一般用socket创建,服务器端用serversocket创建数据传输用的还是IO流,所以,我们发现它们抛出的异常父类也是IO父类下面,先看客户端代码:public static void main... -
Linux高性能服务器编程-高清-pdf
2014-10-18 09:55:37《Linux高性能服务器编程》是Linux服务器编程领域的经典著作,由资深Linux软件开发工程师撰写,从网络协议、服务器编程核心要素、原理机制、工具框架等多角度全面阐释了编写高性能Linux服务器应用的方法、技巧和思想... -
QT网络编程——TCP客户端连接到服务器
2020-05-24 01:12:54Qt使用QtNetwork模块来进行网络编程,提供了一层统一的套接字抽象用于编写不同层次的网络程序,避免了应用套接字进行网络编的繁琐(因有时需引用底层操作系统的相关数据结构)。有较底层次的类如QTcpSocket、... -
网络编程(一)—— 网络编程模型 & 基础概念
2022-03-24 19:06:54文章目录客户端 - 服务器网络编程模型IP地址 & 端口号子网掩码数据报 & 字节流PROBLEM 客户端 - 服务器网络编程模型 “Client - Server”模型的操作概念可以类比我们用淘宝进行购物的操作,每一次的在手机...