网络编程
linux-dash
A beautiful web dashboard for Linux
项目地址:https://gitcode.com/gh_mirrors/li/linux-dash
免费下载资源
·
网络编程
统一声明:
博客转载 声 明 : 本博客部分内容来源于网络、书籍、及各类手册。
内容宗旨为方便查询、总结备份、开源分享。
部分转载内容均有注明出处,如有侵权请联系博客告知并删除,谢谢!
百度云盘提取码:统一提取码:ziyu
个人网站入口:http://www.baiziqing.cn/
一、TCP
1.1、网络基础理论知识
1、早期的ARPAnet使用ncp控制协议
2、TCP / IP协议
TCP:用来检测网络传输中的差错问题。
IP :用于互联不同网络的主机。
TCP/IP协议是iniernet网络中的“世界语”
3、网络体系结构:网络层次结构和协议的集合。
网络体系结构分为osi模型和tcp/ip模型。 ***
osi模型7层结构:应用层、表示层、会话层、传输层、网络层、数据链路层、物理层。
tcp/ip体系结构:应用层、 传输层、网络层、网络接口和物理层。
应用层:http
传输层:tcp, udp
网络层: ip, icmp
网络接口层(数据链路与物理层): 以太网
4、TCP/IP是协议是传输控制协议,以称为网络通信协议。
tcp/IP协议协议族:
TCP:传输控制协议。
IP:网间协议
UDP:数据报协议
SMTP:邮件传输协议
HTTP:超文本传输协议
5、UDP和TCP ***
共同点:同为传输层协议
不同点:
TCP:面向有连接,可靠(保证数据可靠<数据无失序、无重复、无丢失到达>)
UDP:面向无连接,不保证数据可靠
TCP:是一种面向连接的传输协议,提供高可靠通信(数据无丢失,数据无误,数据无失序)
使用场景:对传输质量要求较高,传输大量数据的通信
UDP:用户数据报协议,是不可靠的无连接协议。
使用场景: 1.传输小尺寸数据
2.应答困难的网络中(无线网络)
3.QQ点对点的文件传输和音视频传输
4.网络电话、流媒体
5.广播、组播
1.2、编程实现网络通信(搭建服务器与客户端)(tcp)
1、socket:是一个编程接口,也是一种(网卡设备文件的)文件面试符。
Linux下的网络编程就是套接字编程。
2、socket类型:
1.流式套接字(SOCK_STREAM):主要用于:TCP传输控制协议
2.数据报套接字(SOCK_DGRAM):主要用于:UDP传输控制协议
3.原始套接字(SOCK_RAW):较低层协议,如IP,ICMP直接访问
3、IP地址:(表示主机)
(1)、网络中标识主机。分为32位(IPV4)和128位(IPV6)
(2)、以点分形式体现。“192.168.2.6”
(3)、分类:以二进制的前8位进行区分
A类:0 ~ 127.255.255.255
B类:128 ~ 191.255.255.255
C类:192 ~ 223.255.255.255
D类:224 ~ 239.255.255.255
E类:240 ~ 255.255.255.255
网络号 1字节 主机号 3字节 A类
网络号 2字节 主机号 2字节 B类
网络号 3字节 主机号 1字节 C类
(4)、将字符串1转换成网络字节序二进制
intet_aton:将点分形式的字符串转换成 网络字节序二进制。
intet_addr:将点分形式的字符串转换成 网络字节序二进制,
并得到转换后的地址。
intet_ntoa:将网络字节序二进制转换成 点分形式的字符串。
4、端口号:(区分进程)
(1)、处理网络数据的进程号
(2)、端口号:2字节(0~65532)
5、字节序:
主机字节序和网络字节序
字节存储方式分为两类: ***
小端序:(小端存储)低序字节存储在低地址。(pc机通用小端序)
大端序:(大端存储)高序字节存储在低地址。(网络传输大端序)网络字节序属于大端。
eg:
0x112233 :高序字节为11,低序字节为33
(1)、字节序转换:
小端序转大端序(主机字节序转网络字节序)
htons、htonl
大端序转小端序(网络字节序转主机字节序)
ntohs、ntohl
6.、服务器:也称为下位机软件,目的是为客户端提供1数据交互等服务。
客户端:也称为上位机软件,目的是与用户进行直接交互。
7、基于tcp的服务器和客户端编程。 ***
查看本地ip地址:ifconfig
(1)、服务器基本框架流程:
1.创建套接字、<socket>
(打开网卡设备文件,设置传输控制协议,得到文件描述符,也就是套接字)
2.绑定套接字、<bind>
(将本机ip地址、端口与套接字进行绑定,告诉接收数据发起者的ip和处理数据的进程号)
3.启动监听、<listen>
(启动监听,设置监听(检测是否有客户端连接),设置服务器同一时刻能接收的最大连接请求数)
4.等待接收客户端连接、<accept>
(阻塞程序,当有客户端发起连接请求,就从队列中按顺序接收客户端连接)
5.数据交互、<read/write、recv/send>
6.关闭套接字、<close>
(2)、客户端流程框架
1.创建套接字、<socket>
(打开网卡设备文件,设置传输控制协议,得到文件描述符,也就是套接字)
2.绑定套接字(可选)
(//填写1 要连接服务器的 ip地址和端口号)
3.建立连接(连接到服务器)、<connect> **
(发起连接请求,这里需要三次握手)
4.数据交互、<read/write、recv/send>
5.关闭套接字、<close>
8、tcp服务器和客服端实现:
/*搭建tcp服务器端*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
int main(int argc, const char *argv[])
{
int sockfd = 0; //监听套接字
int connfd = 0; //连接套接字
int ret = 0;
/*1.创建套接字(tcp)*/
sockfd = socket(AF_INET, SOCK_STREAM, 0); //创建;买手机
if(sockfd == -1) //监听套接字
{
perror("socket");
return -1;
}
/*2.绑定套接字*/
struct sockaddr_in seraddr;
seraddr.sin_family = AF_INET; //协议族
seraddr.sin_port = htons(6666); //端口消息
seraddr.sin_addr.s_addr = inet_addr("192.168.7.146");
/*绑定地址消息(关联socket + 地址消息)*/
ret = bind(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr)); //绑定;插卡
if(ret == -1)
{
perror("bind");
exit(-1);
}
else
printf("bind success\n");
/*3.启动监听、也就是启动服务器*/
listen(sockfd, 32); //监听;开机
while(1)
{
printf("wait for the connection: ");
/*4.等待接收客户端连接*/
connfd = accept(sockfd, NULL, NULL); //等待;接听
if(connfd == -1) //连接套接字
{
perror("accept");
exit(-1);
}
printf("connect a client\n");
char buf[512];
/*5.数据交互*/
while(1)
{
memset(buf, 0, sizeof(buf)); //清空buf
ret = recv(connfd, buf, sizeof(buf), 0); //交互;接收
//buf[ret] = '\0'; //清空buf
printf("recv: %d bytes, buf: %s\n", ret, buf);
if(ret <= 0)
{
printf("client quit\n");
break;
}
}
/*6.关闭套接字*/
close(connfd);//关闭监听套接字;挂断
}
close(socket);//关闭连接套接字;挂断
return 0;
}
/*搭建tcp客户端*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
int main(int argc, const char **argv[])
{
int sockfd = 0; //监听套接字
int connfd = 0; //连接套接字
int ret = 0;
/*1.创建套接字(tcp)*/
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd == -1) //监听套接字
{
perror("socket");
return -1;
}
/*2.绑定套接字*/
struct sockaddr_in seraddr;
seraddr.sin_family = AF_INET; //协议族
seraddr.sin_port = htons(6666); //端口消息
seraddr.sin_addr.s_addr = inet_addr("192.168.7.146");
/*3.建立连接*/
ret = connect(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr)); //连接
if(ret == -1)
{
perror("connect");
exit(-1);
}
printf("connect to service\n");
/*数据交换*/
char buf[512];
while(1)
{
gets(buf);
if(*buf == 'q')
{
printf("quit\n");
break;
}
send(sockfd, buf, sizeof(buf), 0);//交换
}
/*关闭套接字*/
close(sockfd);
return 0;
}
/输出功能:客户端连接服务器,客服端输出什么,服务器端返回什么,输入字符q退出/
9、测试工具:
(1)、TcpUdpDebug
(2)、telnet 命令
telnet 192.168.7.199 7777
注意:输入 ctrl + ] + q 退出
练习:
1.实现一个时间服务器,客户端发送time,服务器返回当前时间。
//搭建TCP服务器
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
//搭建TCP服务器
int main(void)
{
int sockfd; //监听套接字
int connfd; //连接套接字
int ret;
sockfd = socket(AF_INET,SOCK_STREAM,0); //创建套接字 (TCP)
struct sockaddr_in seraddr;
seraddr.sin_family = AF_INET; //协议族
seraddr.sin_port = htons(2222);//端口
seraddr.sin_addr.s_addr = inet_addr("192.168.7.199");
ret = bind(sockfd,(struct sockaddr *)&seraddr,sizeof(seraddr)); //绑定地址信息(关联sockfd + 地址信息)
if(ret==-1){
perror("bind");
exit(-1);
}
printf("bind success\n");
listen(sockfd,32); //监听
while(1)
{
printf("wait for client...\n");
connfd = accept(sockfd,NULL,NULL); //建立连接
if(connfd==-1){
perror("accept");
exit(-1);
}
printf("connect a client\n");
char buf[512];
while(1){
memset(buf,0,sizeof(buf));
ret = recv(connfd,buf,sizeof(buf),0); //接收
if(ret<=0){
printf("client quit\n");
break;
}
printf("recv:%d bytes,buf:%s\n",ret,buf);
//解析客户端请求 根据请求给响应数据
if(0==strcmp(buf,"get 1.txt")){
#if 0
char data[512];
FILE *fp = fopen("1.txt","r");
ret = fread(data,1,sizeof(data),fp);
send(connfd,data,ret,0);
#endif
#if 1
time_t val = time(NULL) ;
char *ptr = ctime(&val);
char data[512];
strcpy(data,ptr);
send(connfd,data,sizeof(data),0);
#endif
}
}
close(connfd); //关闭connfd
}
close(sockfd); //关闭sockfd
return 0;
}
//搭建TCP客户端
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
//搭建TCP服务器
int main(int argc,char **argv)
{
int sockfd; //监听套接字
int connfd; //连接套接字
int ret;
sockfd = socket(AF_INET,SOCK_STREAM,0); //创建套接字 (TCP)
struct sockaddr_in seraddr;
seraddr.sin_family = AF_INET; //协议族
seraddr.sin_port = htons(2222);//端口
seraddr.sin_addr.s_addr = inet_addr("192.168.7.199");
ret = connect(sockfd,(struct sockaddr *)&seraddr,sizeof(seraddr)); //连接
if(ret == -1){
perror("connect");
exit(-1);
}
printf("connect to service\n");
char buf[512];
char data[512];
while(1){
gets(buf);
if(*buf=='Q'){
printf("quit\n");
break;
}
send(sockfd,buf,sizeof(buf),0);
if(0==strcmp(buf,"time")){
memset(data,0,sizeof(data));
ret = recv(sockfd,data,sizeof(data),0);
if(ret<=0){
break;
}
printf("client_recv:%s\n",data);
}
}
close(sockfd);
return 0;
}
10、扩展: 0表示本机地址:
seraddr.sin_addr.s_addr = inet_addr("0.0.0.0");
seraddr.sin_addr.s_addr = inet_addr("0");
设置地址重用:setsokopt
绑定之前设置:int on = 1;
setsokopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
记录对方地址信息:
struct sockaddr cliaddr;
int len = sizeof(cliaddr);
connfd = accept(sockfd, (struct sockaddr *)&cliaddr, &len); //等待;接听
printf("connect a client ip:%s,port:%u \n",inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port) );
11、tcp客户和服务器端架构:
//搭建TCP服务器
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
//搭建TCP服务器
int main(void)
{
int sockfd; //监听套接字
int connfd; //连接套接字
int ret;
sockfd = socket(AF_INET,SOCK_STREAM,0); //创建套接字 (TCP)
struct sockaddr_in seraddr; //服务器地址信息
struct sockaddr_in cliaddr; //记录客户端地址
seraddr.sin_family = AF_INET; //协议族
seraddr.sin_port = htons(2222);//端口
seraddr.sin_addr.s_addr = inet_addr("0"); //0地址代表本机
int on = 1;
setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));
/* 设置地址重用 */
ret = bind(sockfd,(struct sockaddr *)&seraddr,sizeof(seraddr)); //绑定地址信息(关联sockfd + 地址信息)
if(ret==-1){
perror("bind");
exit(-1);
}
printf("bind success\n");
listen(sockfd,32); //监听
int len = sizeof(cliaddr);
while(1)
{
printf("wait for client...\n");
connfd = accept(sockfd,(struct sockaddr *)&cliaddr,&len); //建立连接
if(connfd==-1){
perror("accept");
exit(-1);
}
printf("connect a client ip:%s,port:%u \n",inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port) );
//解析出 客户端的 ip与端口
char buf[512];
while(1){
memset(buf,0,sizeof(buf));
ret = recv(connfd,buf,sizeof(buf),0); //接收
if(ret<=0){
printf("client quit\n");
break;
}
printf("recv:%d bytes,buf:%s\n",ret,buf);
//业务逻辑
//解析客户端请求 根据请求给响应数据
}
close(connfd); //关闭connfd
}
close(sockfd); //关闭sockfd
return 0;
}
//搭建TCP客户端
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
//搭建TCP服务器
int main(int argc,char **argv)
{
int sockfd; //监听套接字
int connfd; //连接套接字
int ret;
sockfd = socket(AF_INET,SOCK_STREAM,0); //创建套接字 (TCP)
struct sockaddr_in seraddr;
seraddr.sin_family = AF_INET; //协议族
seraddr.sin_port = htons(5555);//端口
seraddr.sin_addr.s_addr = inet_addr("192.168.7.172");
ret = connect(sockfd,(struct sockaddr *)&seraddr,sizeof(seraddr)); //连接
if(ret == -1){
perror("connect");
exit(-1);
}
printf("connect to service\n");
char buf[512];
while(1){
gets(buf);
if(*buf=='Q'){
printf("quit\n");
break;
}
send(sockfd,buf,sizeof(buf),0);
}
close(sockfd);
return 0;
}
二、UDP
2.1、UDP编程
1、 搭建udp服务器
socket-->bind-->recvfrom/sendto-->close
2、搭建udp客户端
socket-->bind(可选)-->recvfrom/sendto-->close
3、相关工具编译:
nc -u 192.168.7.199 7777 // 测试udp服务器
nc -u 244.10.10.10 7777 // 发送组播消息
//搭建:udp服务器
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
int main(void)
{
int sockfd;
int ret;
sockfd = socket(AF_INET,SOCK_DGRAM,0); //创建套接字(udp)
struct sockaddr_in seraddr,cliaddr;
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(7777);
seraddr.sin_addr.s_addr = inet_addr("0"); //0地址代表本机
int on=1;
setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on)); //设置地址重用
ret = bind(sockfd,(struct sockaddr *)&seraddr,sizeof(seraddr)); //绑定地址信息( sockfd + addr )
if(ret==-1){
perror("bind");
exit(-1);
}
char buf[512];
int len = sizeof(cliaddr);
while(1){
memset(buf,0,sizeof(buf));
ret = recvfrom(sockfd,buf,sizeof(buf),0,(struct sockaddr *)&cliaddr,&len); //接收数据
if(ret==-1){
break;
}
printf("recv:%d bytes,buf:%s\n",ret,buf);
if(0==strcmp(buf,"time")){
time_t val = time(NULL);
char *ptr = ctime(&val);
char data[512];
strcpy(data,ptr);
sendto(sockfd,data,sizeof(data),0,(struct sockaddr *)&cliaddr,len);
}
//sendto();
}
close(sockfd); //关闭
return 0;
}
//搭建:udp客户端
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
//搭建:udp客户端
int main(int argc,char **argv)
{
if(argc!=2){
printf("usage:%s + ip\n",argv[0]);
exit(-1);
}
int sockfd;
int ret;
sockfd = socket(AF_INET,SOCK_DGRAM,0); //创建套接字(udp)
struct sockaddr_in seraddr;
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(7777);
seraddr.sin_addr.s_addr = inet_addr(argv[1]);
char buf[512];
while(1){
gets(buf);
if(*buf == 'Q'){
printf("quit\n");
break;
}
sendto(sockfd,buf,sizeof(buf),0,(struct sockaddr *)&seraddr,sizeof(seraddr));
}
close(sockfd);
return 0;
}
2.2、广播与组播
1 、广播
发送广播消息:
1.向广播地址发送 //192.168.7.255
2.开广播权限
接收广播消息:
1.绑定0地址 或 广播地址
2.端口一致
//接收广播消息
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
//接收广播消息
//1.绑定0地址 或 广播地址
//2.端口一致
int main(void)
{
int sockfd;
int ret;
sockfd = socket(AF_INET,SOCK_DGRAM,0); //创建套接字(udp)
struct sockaddr_in seraddr,cliaddr;
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(7777);
seraddr.sin_addr.s_addr = inet_addr("0"); //0地址代表本机
int on=1;
setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on)); //设置地址重用
ret = bind(sockfd,(struct sockaddr *)&seraddr,sizeof(seraddr)); //绑定地址信息( sockfd + addr )
if(ret==-1){
perror("bind");
exit(-1);
}
char buf[512];
int len = sizeof(cliaddr);
while(1){
memset(buf,0,sizeof(buf));
ret = recvfrom(sockfd,buf,sizeof(buf),0,(struct sockaddr *)&cliaddr,&len); //接收数据
if(ret==-1){
break;
}
printf("recv:%d bytes,buf:%s\n",ret,buf);
if(0==strcmp(buf,"time")){
time_t val = time(NULL);
char *ptr = ctime(&val);
char data[512];
strcpy(data,ptr);
sendto(sockfd,data,sizeof(data),0,(struct sockaddr *)&cliaddr,len);
}
//sendto();
}
close(sockfd); //关闭
return 0;
}
//发送广播消息
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
//发送广播消息
//1.向广播地址发送
//2.开广播权限
int main(int argc,char **argv)
{
if(argc!=2){
printf("usage:%s + ip\n",argv[0]);
exit(-1);
}
int sockfd;
int ret;
sockfd = socket(AF_INET,SOCK_DGRAM,0); //创建套接字(udp)
struct sockaddr_in seraddr;
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(7777);
seraddr.sin_addr.s_addr = inet_addr(argv[1]);
/* 打开广播权限 */
int on = 1;
setsockopt(sockfd,SOL_SOCKET,SO_BROADCAST,&on,sizeof(on));
char buf[512];
while(1){
gets(buf);
if(*buf == 'Q'){
printf("quit\n");
break;
}
ret = sendto(sockfd,buf,sizeof(buf),0,(struct sockaddr *)&seraddr,sizeof(seraddr));
if(ret==-1){
perror("sendto");
exit(-1);
}
}
close(sockfd);
return 0;
}
2、组播
组播的地址范围224.0.0.0 到 239.255.255.255
发送组播消息:
1.向组播地址发送 //224.10.10.10
接收组播消息:
1.绑定0地址 或 组播地址
2.加入多播组
3.端口一致
//接收组播消息
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
//接收组播消息
//1.绑定0地址 或 组播地址
//2.加入多播组
//3.端口一致
int main(void)
{
int sockfd;
int ret;
sockfd = socket(AF_INET,SOCK_DGRAM,0); //创建套接字(udp)
struct sockaddr_in seraddr,cliaddr;
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(7777);
seraddr.sin_addr.s_addr = inet_addr("0"); //0地址代表本机
//加入多播组 224.10.10.10
#if 0
struct ip_mreqn {
struct in_addr imr_multiaddr; /* IP multicast group
address */
struct in_addr imr_address; /* IP address of local
interface */
int imr_ifindex; /* interface index */
};
#endif
struct ip_mreqn mreq;
memset(&mreq,0,sizeof(mreq));
mreq.imr_multiaddr.s_addr = inet_addr("224.10.10.10"); //组播地址
mreq.imr_address.s_addr = inet_addr("0"); //本机地址
setsockopt(sockfd,IPPROTO_IP,IP_ADD_MEMBERSHIP,&mreq,sizeof(mreq));
int on=1;
setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on)); //设置地址重用
ret = bind(sockfd,(struct sockaddr *)&seraddr,sizeof(seraddr)); //绑定地址信息( sockfd + addr )
if(ret==-1){
perror("bind");
exit(-1);
}
char buf[512];
int len = sizeof(cliaddr);
while(1){
memset(buf,0,sizeof(buf));
ret = recvfrom(sockfd,buf,sizeof(buf),0,(struct sockaddr *)&cliaddr,&len); //接收数据
if(ret==-1){
break;
}
printf("recv:%d bytes,buf:%s\n",ret,buf);
if(0==strcmp(buf,"time")){
time_t val = time(NULL);
char *ptr = ctime(&val);
char data[512];
strcpy(data,ptr);
sendto(sockfd,data,sizeof(data),0,(struct sockaddr *)&cliaddr,len);
}
//sendto();
}
close(sockfd); //关闭
return 0;
}
//发送组播消息
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
//发送组播消息
//1.向组播地址发送 //224.10.10.10
int main(int argc,char **argv)
{
if(argc!=2){
printf("usage:%s + ip\n",argv[0]);
exit(-1);
}
int sockfd;
int ret;
sockfd = socket(AF_INET,SOCK_DGRAM,0); //创建套接字(udp)
struct sockaddr_in seraddr;
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(7777);
seraddr.sin_addr.s_addr = inet_addr(argv[1]);
char buf[512];
while(1){
gets(buf);
if(*buf == 'Q'){
printf("quit\n");
break;
}
ret = sendto(sockfd,buf,sizeof(buf),0,(struct sockaddr *)&seraddr,sizeof(seraddr));
if(ret==-1){
perror("sendto");
exit(-1);
}
}
close(sockfd);
return 0;
}
练习:
1.完成网络实验二
2.熟悉udp编程,组播,广播
3.复习进程,线程
2.3、setsockopt 的基本用法
/*********************** setsockopt 的基本用法****************/
/* 设置地址重用 防止异常退出 */
int on = 1;
setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));
/* 打开广播权限 创建广播就下面两句 */
int on = 1;
setsockopt(sockfd,SOL_SOCKET,SO_BROADCAST,&on,sizeof(on));
/* 加入多播组 创建多播就下面五句 */
struct ip_mreqn mreq;
memset(&mreq,0,sizeof(mreq));
mreq.imr_multiaddr.s_addr = inet_addr("224.10.10.10"); //组播地址
mreq.imr_address.s_addr = inet_addr("0"); //本机地址
setsockopt(sockfd,IPPROTO_IP,IP_ADD_MEMBERSHIP,&mreq,sizeof(mreq)); //退出组播
/*********************** setsockopt 的基本用法****************/
2.4、广播与组播
1、广播与组播只能用udp协议.
tcp与udp
tcp:面向连接(三次握手),保证数据可靠,完整性
udp:无连接(没有三次握手)
2、广播
广播地址:在局域网内部,都是主机号全为1的形式
例如: 192.168.7.122. 对应的广播地址: 192.168.7.255
广播是一对多的服务,从广播服务器发送的数据,
所有该网段的主机都能收到.
发送端:
{
创建套接字. ---socket() //SOCK_DGRAM
绑定地址信息. bind(). struct sockaddr_in;
设置广播地址信息. struct sockaddr_in.
开启广播服务. setsockopt()
发送广播数据sendto().
}
接收端:
{
创建套接字. ---socket() //SOCK_DGRAM
绑定地址信息. bind(). struct sockaddr_in;
(绑定的端口要和发送端指定的发送端口一致才能接收解析数据)
(绑定的目的是为了显示自己的端口以及ip信息)
接受广播数据.
}
3、组播(多播)
由于广播默认发给所有局域网内的主机,因此容易造成网络风暴.
解决该问题可以用组播: 只给加入到组播组内的成员发送消息.
D类ip地址通常被用于组播地址:
224.0.0.1 ~ 239.255.255.255
但是有的是被预留了,有的是用于互联网,所以这些不能直接使用.
用户可用: 224.0.2.0 ~ 238.255.255.255.(临时使用,全网有效)
239.0.0.0 ~ 239.255.255.255.(本地常用,局域网)
组播发送端
{
创建套接字. ---socket() //SOCK_DGRAM
绑定地址信息. bind(). struct sockaddr_in;
设置组播地址信息. struct sockaddr_in.
开启组播服务. setsockopt() //允许开启组播服务:IP_MULTICAST_IF
发送广播数据sendto()
}
组播接收端
{
创建套接字. ---socket() //SOCK_DGRAM
绑定地址信息. bind(). struct sockaddr_in;
加入组播组当中. setsockopt(). //IP_ADD_MEMBERSHIP
接收数据.
}
SOL_SOCKET
-----------------------------------------------------
参数optname 宏的作用 对应参数optaval的类型
SO_BROADCAST 允许发送广播数据 int
SO_DEBUG 允许调试 int
SO_DONTROUTE 不查找路由 int
SO_ERROR 获得套接字错误 int
SO_KEEPALIVE 保持连接 int
SO_LINGER 延迟关闭连接 struct linger
SO_OOBINLINE 带外数据放入正常数据流 int
SO_RCVBUF 接收缓冲区大小 int
SO_SNDBUF 发送缓冲区大小 int
SO_RCVLOWAT 接收缓冲区下限 int
SO_SNDLOWAT 发送缓冲区下限 int
SO_RCVTIMEO 接收超时 struct timeval
SO_SNDTIMEO 发送超时 struct timeval
SO_REUSEADDR 允许重用本地地址和端口 int
SO_TYPE 获得套接字类型 int
SO_BSDCOMPAT 与BSD系统兼容 int
======================================================
IPPROTO_IP
------------------------------------------------------
IP_ADD_MEMBERSHIP 加入到组播组中 struct ip_mreq
IP_MULTICAST_IF 允许开启组播报文的接口 struct ip_mreq
1. select(): 针对所有文件描述符表中的io操作,
检测一定时间内是否有任何相关的io操作
2.setsockopt(listenfd): 检测监听套接字,在一定时间内是否有客户端连接
3.setsockopt(connfd): 检测通信套接字,在一定时间内是否有客户端发送消息
三、并发服务器
3.1、循环服务器
1、循环服务器:同一时间,只能处理一个客户,通过轮询来处理多个客户的请求
2、并发服务器:同一时间,能够处理多个客户
3.2、并发服务器
1、多进程并发服务器
基本原理: 每连接一个客户端,创建一个子进程,子进程负责处理connfd(客户请求)
父进程处理sockfd(连接请求)。
//注意:子进程结束时,需要进行资源回收
/*多进程并发服务器*/
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
//多进程并发服务器
int tcpser_init(const char *ip,int port);
int do_client(int connfd);
void signal_handler(int sig);
int main(void)
{
int connfd; //连接套接字
int sockfd; //监听套接字
struct sockaddr_in cliaddr; //记录客户端地址
int len = sizeof(cliaddr);
sockfd = tcpser_init("0",6666); //调用初始化函数
signal(SIGCHLD,signal_handler); //1.注册SIGCHIL信号,设定好信号处理函数
while(1)
{
printf("wait for client...\n");
connfd = accept(sockfd,(struct sockaddr *)&cliaddr,&len); //建立连接
if(connfd==-1){
if(errno == EINTR){ //错误码判断,如果是接继续执行不退出
continue;
}
else{
perror("accept");
exit(-1);
}
}
printf("connect a client ip:%s,port:%u \n",inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port) );
//每连接一个客户,创建一个子进程
pid_t pid = fork();
if(pid==0){
close(sockfd); //关闭监听
do_client(connfd); //子进程去处(connfd)理客户请求
exit(0);
}
else{
close(connfd); //关闭连接
continue; //父进程去处(sockfd)理客户连接
}
}
return 0;
}
/*tcp初始化函数*/
int tcpser_init(const char *ip,int port)
{
int sockfd; //监听套接字
int connfd; //连接套接字
int ret;
sockfd = socket(AF_INET,SOCK_STREAM,0); //创建套接字 (TCP)
struct sockaddr_in seraddr; //服务器地址信息
seraddr.sin_family = AF_INET; //协议族
seraddr.sin_port = htons(port);//端口
seraddr.sin_addr.s_addr = inet_addr(ip); //0地址代表本机
int on = 1;
setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));
/* 设置地址重用 */
ret = bind(sockfd,(struct sockaddr *)&seraddr,sizeof(seraddr)); //绑定地址信息(关联sockfd + 地址信息)
if(ret==-1){
perror("bind");
exit(-1);
}
printf("bind success\n");
listen(sockfd,32); //监听
return sockfd;
}
/*用户处理函数*/
int do_client(int connfd)
{
//处理客户的 请求与响应
char buf[512];
int ret;
while(1){
memset(buf,0,sizeof(buf));
ret = recv(connfd,buf,sizeof(buf),0); //接收
if(ret<=0){
printf("client quit\n");
break;
}
printf("recv:%d bytes,buf:%s\n",ret,buf);
//业务逻辑
}
}
/*信号处理函数*/
void signal_handler(int sig)
{
while(waitpid(-1,NULL,WNOHANG)>0); //当产生晓得僵尸进程时,需要while清理
}
2、多线性并发服务器
基本原理: 每连接一个客户端,创建一个子线程,子线程负责处理connfd(客户请求)
主线程处理sockfd(连接请求)。
//注意:子线程结束时,需要进行资源回收
/*多线程并发服务器*/
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
#include <pthread.h>
//多线程并发服务器
int tcpser_init(const char *ip,int port);
void * do_client(void *arg);
int main(void)
{
int connfd; //连接套接字
int sockfd; //监听套接字
struct sockaddr_in cliaddr; //记录客户端地址
int len = sizeof(cliaddr);
sockfd = tcpser_init("0",6666);
while(1)
{
printf("wait for client...\n");
connfd = accept(sockfd,(struct sockaddr *)&cliaddr,&len); //建立连接
if(connfd==-1){
perror("accept");
exit(-1);
}
printf("connect id:%d a client ip:%s,port:%u \n",connfd,inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port) );
//每连接一个客户,创建一个子线程
pthread_t tid;
pthread_create(&tid, NULL, do_client, (void *)connfd);
}
return 0;
}
int tcpser_init(const char *ip,int port)
{
int sockfd; //监听套接字
int connfd; //连接套接字
int ret;
sockfd = socket(AF_INET,SOCK_STREAM,0); //创建套接字 (TCP)
struct sockaddr_in seraddr; //服务器地址信息
seraddr.sin_family = AF_INET; //协议族
seraddr.sin_port = htons(port);//端口
seraddr.sin_addr.s_addr = inet_addr(ip); //0地址代表本机
int on = 1;
setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));
/* 设置地址重用 */
ret = bind(sockfd,(struct sockaddr *)&seraddr,sizeof(seraddr)); //绑定地址信息(关联sockfd + 地址信息)
if(ret==-1){
perror("bind");
exit(-1);
}
printf("bind success\n");
listen(sockfd,32); //监听
return sockfd;
}
void * do_client(void *arg)
{
pthread_detach( pthread_self() ); //子线程 资源回收
int connfd = (int)arg;
//处理客户的 请求与响应
char buf[512];
int ret;
while(1){
memset(buf,0,sizeof(buf));
ret = recv(connfd,buf,sizeof(buf),0); //接收
if(ret<=0){
printf("client quit id:%d\n",connfd);
close(connfd);
pthread_exit(NULL);
}
printf("recv:%d bytes,buf:%s\n",ret,buf);
//业务逻辑
}
}
3.3、IO多路复用机制(select、poll、epoll)
基本原理:
先构造一张有关描述符的表,然后调用一个函数。
当这些文件描述符中的一个或多个已准备好进行I/O时函数才返回。
函数返回时告诉进程那个描述符已就绪,可以进行I/O操作。
//相关博客
//https://www.cnblogs.com/shengguorui/p/11949282.html
//https://www.jianshu.com/p/397449cadc9a
select函数
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
/*********************************************************************************
* Description: select机制
* Input: nfds: 最大文件描述符值+1
readfds: 读事件
writefds: 写事件
exceptfds:异常事件
timeout:超时值
* Return: 成功返回 准备好的文件描述符个数
0:超时返回
* Others: -1:出错
**********************************************************************************/
void FD_CLR(int fd, fd_set *set); //删除 (清除)
int FD_ISSET(int fd, fd_set *set); //是否准备好
void FD_SET(int fd, fd_set *set); //添加
void FD_ZERO(fd_set *set); //清空
/*通过io多路复用: 实现了 "同时" 读3根管道*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
//通过io多路复用: 实现了 "同时" 读3根管道
int main(void)
{
char buf[128];
int fd1 = open("f1",O_RDWR);
int fd2 = open("f2",O_RDWR);
int fd3 = open("f3",O_RDWR);
//1.准备一张表(读事件)
fd_set readfds;
fd_set tmpfds;
//2.把文件描述符添加到 表中
FD_ZERO(&readfds); //清空表
FD_SET(fd1,&readfds); //添加fd1
FD_SET(fd2,&readfds); //添加fd2
FD_SET(fd3,&readfds); //添加fd3
tmpfds = readfds;
int maxfd = fd3;
int ret;
int i;
while(1){
readfds = tmpfds ; //修正readfds
ret = select(maxfd+1,&readfds,NULL,NULL,NULL); //阻塞 轮询检测
//循环 找到准备好的管道
for(i=fd1; i<maxfd+1;i++){
if(FD_ISSET(i,&readfds)){
//读管道数据
memset(buf,0,sizeof(buf));
read(i,buf,sizeof(buf));
printf("%s\n",buf);
}
}
}
return 0;
}
练习:
1.熟练掌握并发服务器
2.熟悉select函数的基本使用
四、IO多路复用
IO多路复用
优点:节约资源
缺点:代码麻烦,不是并发,而是轮询
4.1、Linux下主要有4种i/o模型
1.1、阻塞I/O:最常用、最简单、效率低
1.2、非阻塞I/O:可防止非阻塞在i/o操作上,需要轮询
1.3、I/O多路复用:允许同时对多个I/O进行控制
1.4、信号驱动I/O:一种同步IO,一般通过注册,回调实现
1.5.异步IO:真正的异步IO
//https://www.baidu.com/link?url=EnsmiY8o3v_xZXyFR_APQOqxRHCZrqGY9Zkm4W0uJ_ItTFFy_GuagkPxgBlaBKQXLP79QumrEVGYe-rD2g5I7qK-iOYgGLelWKyM2n5Lot_&wd=&eqid=ec1bce8b000017230000000660efae81
4.2、IO多路复用机制(select、poll、epoll)
基本原理:
先构造一张有关描述符的表,然后调用一个函数。
当这些文件描述符中的一个或多个已准备好进行I/O时函数才返回。
函数返回时告诉进程那个描述符已就绪,可以进行I/O操作。
//https://www.cnblogs.com/Anker/p/3265058.html
1、采用select实现 IO多路复用服务器
/*采用select实现 IO多路复用服务器*/
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
//采用select实现 IO多路复用服务器
int tcpser_init(const char *ip,int port);
int do_client(int connfd);
int main(void)
{
int connfd; //连接套接字
int sockfd; //监听套接字
char buf[256];
struct sockaddr_in cliaddr; //记录客户端地址
int len = sizeof(cliaddr);
int ret;
sockfd = tcpser_init("0",6666);
/*准备一张表*/
fd_set readfds,tmpfds;
/*向表中 添加文件描述符 */
FD_ZERO(&readfds);
FD_SET(sockfd,&readfds);
tmpfds = readfds;
int maxfd = sockfd;
int i;
//select 轮询检测
while(1){
readfds = tmpfds; //修正readfds
ret = select(maxfd+1,&readfds,NULL,NULL,NULL);
if(ret==-1){
perror("select");
exit(-1);
}
for(i=sockfd;i<maxfd+1;i++){
if(FD_ISSET(i,&readfds)){
if(i==sockfd){
/* 建立连接 (sockfd准备好了) */
connfd = accept(sockfd,NULL,NULL);
printf("connect a client :%d\n",connfd);
/* 把connfd 添加到表中 修正maxfd */
FD_SET(connfd,&tmpfds);
if(maxfd<connfd){
maxfd = connfd; //修正maxfd
}
}
else{
printf("connfd:%d 准备好了\n",i);
/* 处理客户请求 (connfd准备好了)*/
memset(buf,0,sizeof(buf));
ret = recv(i,buf,sizeof(buf),0);
if(ret<=0){
printf("client quit %d\n",i);
close(i);
FD_CLR(i,&tmpfds);
}
else{
printf("recv:%s\n",buf);
}
}
}
}
}
return 0;
}
#if 0
while(1)
{
printf("wait for client...\n");
connfd = accept(sockfd,(struct sockaddr *)&cliaddr,&len); //建立连接
if(connfd==-1){
perror("accept");
exit(-1);
}
printf("connect a client id:%d ip:%s,port:%u \n",connfd,inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port) );
#endif
int tcpser_init(const char *ip,int port)
{
int sockfd; //监听套接字
int connfd; //连接套接字
int ret;
sockfd = socket(AF_INET,SOCK_STREAM,0); //创建套接字 (TCP)
struct sockaddr_in seraddr; //服务器地址信息
seraddr.sin_family = AF_INET; //协议族
seraddr.sin_port = htons(port);//端口
seraddr.sin_addr.s_addr = inet_addr(ip); //0地址代表本机
int on = 1;
setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));
/* 设置地址重用 */
ret = bind(sockfd,(struct sockaddr *)&seraddr,sizeof(seraddr)); //绑定地址信息(关联sockfd + 地址信息)
if(ret==-1){
perror("bind");
exit(-1);
}
printf("bind success\n");
listen(sockfd,32); //监听
return sockfd;
}
}
2、通过io多路复用: 实现了 “同时” 读3根管道 采用poll机制
/*通过io多路复用: 实现了 "同时" 读3根管道 采用poll机制*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <poll.h>
//通过io多路复用: 实现了 "同时" 读3根管道 采用poll机制
int main(void)
{
char buf[128];
int fd1 = open("f1",O_RDWR);
int fd2 = open("f2",O_RDWR);
int fd3 = open("f3",O_RDWR);
//1.准备一张表 (结构体)
struct pollfd events[3];
//2.把文件描述符添加到 表中
memset(events,0,sizeof(events)); //清空表
events[0].fd = fd1; //添加fd1
events[0].events = POLLIN; //读事件
events[1].fd = fd2; //添加fd2
events[1].events = POLLIN; //读事件
events[2].fd = fd3; //添加fd3
events[2].events = POLLIN; //读事件
int ret;
int i;
while(1){
ret = poll(events,3,-1);
for(i=0;i<3;i++){
if(events[i].revents & POLLIN ){
int fd = events[i].fd;
printf("%d 准备好了\n",fd);
memset(buf,0,sizeof(buf));
read(fd,buf,sizeof(buf));
printf("%s\n",buf);
}
}
}
return 0;
}
4.3、超时检测。
方法1:设置套接字属性、设置超时值。
/*方法1:设置套接字属性*/
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
//超时检测
//方法1:设置套接字属性
int main(void)
{
int sockfd; //监听套接字
int connfd; //连接套接字
int ret;
sockfd = socket(AF_INET,SOCK_STREAM,0); //创建套接字 (TCP)
struct sockaddr_in seraddr; //服务器地址信息
struct sockaddr_in cliaddr; //记录客户端地址
seraddr.sin_family = AF_INET; //协议族
seraddr.sin_port = htons(6666);//端口
seraddr.sin_addr.s_addr = inet_addr("0"); //0地址代表本机
int on = 1;
setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));
/* 设置地址重用 */
ret = bind(sockfd,(struct sockaddr *)&seraddr,sizeof(seraddr)); //绑定地址信息(关联sockfd + 地址信息)
if(ret==-1){
perror("bind");
exit(-1);
}
printf("bind success\n");
listen(sockfd,50); //监听
int len = sizeof(cliaddr);
while(1)
{
printf("wait for client...\n");
connfd = accept(sockfd,(struct sockaddr *)&cliaddr,&len); //建立连接
if(connfd==-1){
perror("accept");
exit(-1);
}
printf("connect a client ip:%s,port:%u \n",inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port) );
//解析出 客户端的 ip与端口
struct timeval tv={5,0}; //5s //****
setsockopt(connfd,SOL_SOCKET,SO_RCVTIMEO,&tv,sizeof(tv)); //****
//设置接收超时
char buf[512];
while(1){
memset(buf,0,sizeof(buf));
ret = recv(connfd,buf,sizeof(buf),0); //接收
if(ret<=0){
if((errno== EAGAIN )|| (errno == EWOULDBLOCK)){ //****
printf("超时返回\n"); //****
//continue; //****
break; //****
}
printf( "client quit\n");
break;
}
printf("recv:%d bytes,buf:%s\n",ret,buf);
//业务逻辑
}
close(connfd); //关闭connfd
}
close(sockfd); //关闭sockfd
return 0;
}
方法2:设置闹钟
/*方法2:设置闹钟*/
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
//超时检测
//方法2:设置闹钟
void alarm_handler(int sig) //****
{
printf("time out\n");
alarm(5);
}
int main(void)
{
int sockfd; //监听套接字
int connfd; //连接套接字
int ret;
signal(SIGALRM,alarm_handler); //****
sockfd = socket(AF_INET,SOCK_STREAM,0); //创建套接字 (TCP)
struct sockaddr_in seraddr; //服务器地址信息
struct sockaddr_in cliaddr; //记录客户端地址
seraddr.sin_family = AF_INET; //协议族
seraddr.sin_port = htons(6666);//端口
seraddr.sin_addr.s_addr = inet_addr("0"); //0地址代表本机
int on = 1;
setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));
/* 设置地址重用 */
ret = bind(sockfd,(struct sockaddr *)&seraddr,sizeof(seraddr)); //绑定地址信息(关联sockfd + 地址信息)
if(ret==-1){
perror("bind");
exit(-1);
}
printf("bind success\n");
listen(sockfd,50); //监听
int len = sizeof(cliaddr);
while(1)
{
printf("wait for client...\n");
connfd = accept(sockfd,(struct sockaddr *)&cliaddr,&len); //建立连接
if(connfd==-1){
perror("accept");
exit(-1);
}
printf("connect a client ip:%s,port:%u \n",inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port) );
//解析出 客户端的 ip与端口
char buf[512];
while(1){
alarm(5); //****
memset(buf,0,sizeof(buf));
ret = recv(connfd,buf,sizeof(buf),0); //接收
if(ret<=0){
printf( "client quit\n");
break;
}
printf("recv:%d bytes,buf:%s\n",ret,buf);
//业务逻辑
}
close(connfd); //关闭connfd
}
close(sockfd); //关闭sockfd
return 0;
}
方法3:在select函数 或poll函数中设置超时值。
/*方法3:在select函数中 设置超时值 */
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
//超时检测
//方法3:在select函数中 设置超时值
int tcpser_init(const char *ip,int port);
int main(void)
{
int connfd; //连接套接字
int sockfd; //监听套接字
char buf[256];
struct sockaddr_in cliaddr; //记录客户端地址
int len = sizeof(cliaddr);
int ret;
sockfd = tcpser_init("0",6666);
/*准备一张表*/
fd_set readfds,tmpfds;
/*向表中 添加文件描述符 */
FD_ZERO(&readfds);
FD_SET(sockfd,&readfds);
tmpfds = readfds;
int maxfd = sockfd;
int i;
//select 轮询检测
while(1){
struct timeval tv = {5,0};
readfds = tmpfds; //修正readfds
ret = select(maxfd+1,&readfds,NULL,NULL,&tv); //****
if(ret==0){
printf("time out\n");
continue;
}
else if(ret==-1){
perror("select");
exit(-1);
}
for(i=sockfd;i<maxfd+1;i++){
if(FD_ISSET(i,&readfds)){
if(i==sockfd){
/* 建立连接 (sockfd准备好了) */
connfd = accept(sockfd,NULL,NULL); // 从监听队列中取出一个连接
printf("connect a client :%d\n",connfd);
/* 把connfd 添加到表中 修正maxfd */
FD_SET(connfd,&tmpfds);
if(maxfd<connfd){
maxfd = connfd; //修正maxfd
}
}
else{
printf("connfd:%d 准备好了\n",i);
/* 处理客户请求 (connfd准备好了)*/
memset(buf,0,sizeof(buf));
ret = recv(i,buf,sizeof(buf),0);
if(ret<=0){
printf("client quit %d\n",i);
close(i);
FD_CLR(i,&tmpfds);
}
else{
printf("recv:%s\n",buf);
}
}
}
}
}
return 0;
}
int tcpser_init(const char *ip,int port)
{
int sockfd; //监听套接字
int connfd; //连接套接字
int ret;
sockfd = socket(AF_INET,SOCK_STREAM,0); //创建套接字 (TCP)
struct sockaddr_in seraddr; //服务器地址信息
seraddr.sin_family = AF_INET; //协议族
seraddr.sin_port = htons(port);//端口
seraddr.sin_addr.s_addr = inet_addr(ip); //0地址代表本机
int on = 1;
setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));
/* 设置地址重用 */
ret = bind(sockfd,(struct sockaddr *)&seraddr,sizeof(seraddr)); //绑定地址信息(关联sockfd + 地址信息)
if(ret==-1){
perror("bind");
exit(-1);
}
printf("bind success\n");
listen(sockfd,32); //监听
return sockfd;
}
方法2和3只是模拟方法1效果,三者不能等同
练习:
熟悉网络超时检测与io多路复用机制
五、数据库操作
7/16 11:00
结构体3个变量、成员、密码、请求响应
文件拷贝
gcc sqlite3 -lsqlite3
7/16 15:16
创建插入数据库表
7/17 9:20
查看本地ip地址:ifconfig
5.1、数据库操作
1、 数据简介
SQLite的源代码是C,其源代码完全开放。SQLite第一个Alpha版本诞生于2000年5月。
他是一个轻量级的嵌入式数据库。
2、 sqlite3基本命令
//命令以.开头
sqlite3 my.db //打开my.db 不存在则创建
.help
.database
.tables
.schema tablename //查看表的结构
.quit //退出
.exit //退出
3 、sqlite3基本语句(SQL语句)
//sql语句以;结束
//注意常见的数据类型:int, float,text(char)
create table usr (name text, passwd text); //创建表
create table if not exists stuinfo(id int,name text); //不存在则创建表
create table usr (name text primary key, passwd text); //创建表 以name作为主键
insert into usr values ("jack","ps666"); //向表中插入数据
select * from usr; //查询表中数据
select * from usr where name="rose" and passwd="ps777"; //按规则查找
delete from usr where name="rose"; //删除指定记录项
update usr set passwd="abc123xyz" where name="lilei"; //更新表
drop table usr; //删除表
4 、sqlite3接口函数
//注意编译时 指定库文件 -lsqlite3
sqlite3_open(); //打开
sqlite3_close(); //关闭
sqlite3_exec(); //执行sql语句
sqlite3_get_table(); //查询
/* 1.sqlite3 */
#include <stdio.h>
#include <sqlite3.h>
#include <stdlib.h>
int main(void)
{
sqlite3 *db;
int ret;
ret = sqlite3_open("my.db",&db); //打开数据库
if(ret!=SQLITE_OK){
printf("open my.db failed\n");
exit(-1);
}
printf("open my.db success\n");
#if 1
//在数据库中 创建表
char sql[256]="create table if not exists stuinfo(id int,name text);";
char *errmsg;
ret = sqlite3_exec(db,sql,NULL,NULL,&errmsg);
if(ret!=SQLITE_OK){
printf("exec failed:%s\n",errmsg);
exit(-1);
}
#endif
#if 0
//向数据库 插入一条记录
printf("输入id与名字\n");
int id;
char name[32];
scanf("%d %s",&id,name);
//拼凑sql语句
char sql[256];
snprintf(sql,sizeof(sql),"insert into stuinfo values('%d','%s');",id,name);
char *errmsg;
ret = sqlite3_exec(db,sql,NULL,NULL,&errmsg);
if(ret!=SQLITE_OK){
printf("exec failed:%s\n",errmsg);
exit(-1);
}
#endif
sqlite3_close(db); //关闭数据
return 0;
}
/* 2.sqlite3 */
#include <stdio.h>
#include <sqlite3.h>
#include <stdlib.h>
//通过回调实现查询
int call_back(void *para, int f_num, char **f_value, char **f_name)
{
//f_num : 字段数目
//f_value : 结果
//f_name : 字段名
int i;
for(i=0;i<f_num;i++){
printf("%s ",f_value[i]);
}
printf("\n");
return 0;
}
int main(void)
{
sqlite3 *db;
int ret;
ret = sqlite3_open("my.db",&db); //打开数据库
if(ret!=SQLITE_OK){
printf("open my.db failed\n");
exit(-1);
}
printf("open my.db success\n");
#if 1
//在数据库中 创建表
char sql[256]="select * from stuinfo;";
char *errmsg;
ret = sqlite3_exec(db,sql,call_back,NULL,&errmsg);
if(ret!=SQLITE_OK){
printf("exec failed:%s\n",errmsg);
exit(-1);
}
#endif
sqlite3_close(db); //关闭数据
return 0;
}
/* 3.sqlite3 */
#include <stdio.h>
#include <sqlite3.h>
#include <stdlib.h>
//通过get_table实现查询
int main(void)
{
sqlite3 *db;
int ret;
ret = sqlite3_open("my.db",&db); //打开数据库
if(ret!=SQLITE_OK){
printf("open my.db failed\n");
exit(-1);
}
printf("open my.db success\n");
#if 1
//在数据库中 创建表
//char sql[256]="select * from stuinfo where id=2 and name='lily';";
char sql[256]="select * from stuinfo;";
char *errmsg;
char **resultp;//结果
int nrow; //行数
int ncolumn; //列数
ret = sqlite3_get_table(db, sql,&resultp, &nrow, &ncolumn, &errmsg);
if(ret!=SQLITE_OK){
printf("sqlite3_get_table failed:%s\n",errmsg);
exit(-1);
}
printf("nrow:%d,ncolumn:%d\n",nrow,ncolumn);
int i;
int index=2;
//循环遍历 查看结果
for(i=0;i<nrow;i++){
printf("%s : %s \n",resultp[index],resultp[index+1]);
index = index+2;
}
#endif
sqlite3_close(db); //关闭数据
return 0;
}
练习:
1.掌握常用的sql语句
2.熟悉sqlite3编程接口
六、 unix域套接字编程
区分流式与数据报式
6.1、unix域套接字编程(本地进程间通信)
1、UNIX域(流式)套接字服务器端:
socket-->bind-->listen-->accept-->recv/send-->close;
UNIX域(流式)套接字客户端:
socket-->bind(可选)-->connect-->send/recv-->close;
2、UNIX域(数据报)套接字服务器端:
socket-->bind-->recvfrom/sendto-->close
UNIX域(数据报)套接字客户端:
socket-->bind(可选)-->recvfrom/sendto-->close
注意地址信息:
struct sockaddr_un // <sys/un.h>
{
sa_family_t sun_family; //协议族
char sun_path[108]; //套接字文件的路径
};
/*搭建 unix域 流式 服务器*/
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/un.h>
//搭建 unix域 流式 服务器
int main(void)
{
int sockfd; //监听套接字
int connfd; //连接套接字
int ret;
sockfd = socket(AF_UNIX,SOCK_STREAM,0); //创建套接字 (本地)
struct sockaddr_un seraddr; //服务器地址信息
remove("socket");
seraddr.sun_family = AF_UNIX; //协议族 本地通信
strcpy(seraddr.sun_path,"./socket"); //地址 本地文件
ret = bind(sockfd,(struct sockaddr *)&seraddr,sizeof(seraddr)); //绑定地址信息(关联sockfd + 地址信息)
if(ret==-1){
perror("bind");
exit(-1);
}
printf("bind success\n");
listen(sockfd,50); //监听
while(1)
{
printf("wait for client...\n");
connfd = accept(sockfd,NULL,NULL); //建立连接
if(connfd==-1){
perror("accept");
exit(-1);
}
printf("connect a client\n");
char buf[512];
while(1){
memset(buf,0,sizeof(buf));
ret = recv(connfd,buf,sizeof(buf),0); //接收
if(ret<=0){
printf("client quit\n");
break;
}
printf("recv:%d bytes,buf:%s\n",ret,buf);
}
close(connfd); //关闭connfd
}
close(sockfd); //关闭sockfd
return 0;
}
/*搭建 unix 流式套接字 客户端*/
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/un.h>
//搭建 unix 流式套接字 客户端
int main(int argc,char **argv)
{
int sockfd; //监听套接字
int connfd; //连接套接字
int ret;
sockfd = socket(AF_UNIX,SOCK_STREAM,0); //创建套接字 (本地)
struct sockaddr_un seraddr;
seraddr.sun_family = AF_UNIX; //协议族 本地
strcpy(seraddr.sun_path,"./socket"); //地址 本地文件
ret = connect(sockfd,(struct sockaddr *)&seraddr,sizeof(seraddr)); //连接
if(ret == -1){
perror("connect");
exit(-1);
}
printf("connect to service\n");
char buf[512];
while(1){
gets(buf);
if(*buf=='Q'){
printf("quit\n");
break;
}
send(sockfd,buf,sizeof(buf),0);
}
close(sockfd);
return 0;
}
6.2、抓包分析
七、 项目实训
1、项目题目:聊天软件
项目要求:
1.登录注册功能
2.获取在线用户
3.私聊
4.群聊
5.发送离线消息
6.记录聊天数据
涉及知识:
1.Tcp编程或UDP编程
2.Udp广播与组播
3.多进程多线程
4.数据库
5.数据结构
6.进程间通信
2、项目题目:网络词典
项目要求:
1.登录注册功能
2.单词查询
3.历史记录
4.支持多客户端连接
涉及知识:
1.Tcp编程
2.多进程多线程
3.数据库
4.文件操作
3、项目题目:文件服务器(云盘系统)
项目要求:
1.登录注册功能
2.获取服务器上文件列表
3.上传文件与下载文件
4.支持多用户同时登录
5.记录用户上传下载信息
6.支持多客户端连接
涉及知识:
1.TCP编程
2.文件操作
3.多进程多线程
4.数据库
4、项目题目:在线点餐系统
项目要求:
1.用户端实现菜品浏览、点餐、买单、查询等功能
2.管理端实现查看点餐信息、修改、添加新菜品等功能
涉及知识:
1.TCP或UDP编程
2.数据库
3.多进程多线程
网络编程-调试工具及LinuxC函数手册!
网络编程-调试工具及LinuxC函数手册!
链接:https://pan.baidu.com/s/1jXWeGyPuFlV3WW5p3wezcg
提取码:t9m4
跳转:上一篇,IO进线程编程!
跳转:下一篇,C++编程总结!
GitHub 加速计划 / li / linux-dash
10.39 K
1.2 K
下载
A beautiful web dashboard for Linux
最近提交(Master分支:2 个月前 )
186a802e
added ecosystem file for PM2 4 年前
5def40a3
Add host customization support for the NodeJS version 4 年前
更多推荐
已为社区贡献1条内容
所有评论(0)