socket编程使用udp和http
UDP
udp协议:无连接 不可靠的 数据报服务
udp编程流程:

代码实现:
服务器端:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<string.h>
int main(){
int sockfd=socket(AF_INET,SOCK_DGRAM,0);//创建套接字,指定udp服务类型
//SOCK_DREAM是数据报服务
if(-1==sockfd)
exit(1);
struct sockaddr_in caddr,saddr;
memset(&saddr,0,sizeof(saddr));
saddr.sin_family=AF_INET;
saddr.sin_port=htons(6000);
saddr.sin_addr.s_addr=inet_addr("127.0.0.1");
int res=bind(sockfd,(struct sockaddr *)&saddr,sizeof(saddr));
if(-1==res)
{
printf("bind err\n");
exit(1);
}
while(1){
char buff[128]={0};
int len=sizeof(caddr);
int n=recvfrom(sockfd,buff,1,0,(struct sockaddr *)&caddr,&len);
//这里recvfrom较于recv多了个caddr用来存放客户端的ip,,当有客户端发消息过来,接收到后会自动把消息里的ip地址存在caddr里,因为udp没有accept,只有把对方地址存下来,后面sendto发数据才知道对方的ip。
printf("buff=%s\n",buff);
sendto(sockfd,"ok",2,0,(struct sockaddr *)&caddr,sizeof(caddr));
}
close(sockfd);
}
客户端:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
int main(){
int sockfd=socket(AF_INET,SOCK_DGRAM,0);//udp
if(-1==sockfd)
exit(1);
struct sockaddr_in saddr;
memset(&saddr,0,sizeof(saddr));
saddr.sin_family=AF_INET;
saddr.sin_port=htons(6000);
saddr.sin_addr.s_addr=inet_addr("127.0.0.1");
while(1){
printf("input:\n");
char buff[128]={0};
fgets(buff,128,stdin);
if(strncmp(buff,"end",3)==0)
break;
sendto(sockfd,buff,strlen(buff)-1,0,(struct sockaddr*)&saddr,sizeof(saddr));
//这里要用strlen,为了防止在传数据时导致数组越界,把没用的数据发过去
memset(buff,0,128);
int len=sizeof(saddr);
recvfrom(sockfd,buff,128,0,(struct sockaddr*)&saddr,&len);
//这里的saddr是用来存对方的ip等信息,我们也可以不要,给NULL即可,而且这里接受的信息就是服务器的信息,就是上面我们写的内容(6000)(127.0.0.1)
printf("recv:%s\n",buff);
}
close(sockfd);
exit(0);
}
我们分别执行服务器端和客户端后便可发信息了。
这里我们连接好后,不结束客户端的执行,而结束服务器端,结束后又运行服务器段,然后再用客户端发消息给服务器端服务器仍然可以接收到数据。

这里就体现出udp无连接的特点。
当我们运行两个客户端时,两个客户端都发消息,这时服务器端都可以接受到他们发的信息,这是因为recvfrom()并没有指定非要接受哪个套接字发过来的信息,来者不拒,不同于TCP的recv(只能接受传来的套接字的信息)。谁给我IP+prot 发消息,我就接受谁的数据。

我们又将服务器端的代码里的recvfrom(),改成一次只接受一个字节数据,这时我们客户端发送大于一个字节的数据时,我们服务器端只会读一字节,剩下的都丢掉了。所以我们要一次性读完,下一次recvfrom()我们就只会接受下一次传来的数据报,这里就是发送次数=接收次数,就不会存在粘包的问题。(比如我们服务器若发两次数据报,分别发abc和xyz,若abc丢了,那就丢了,服务器端也就只认为你发了个xyz,这就是udp的不可靠,udp的不可靠指丢包后没有进一步的处理,而不是udp总丢包,丢包还是少见的)(而TCP我们发个hello过去,另一端接收到后便会发一个确认报文,该报文没有数据,只是确认收到了,这就是应答确认;udp发送信息后,对方不管接收到否,都不会发信息)


没有缓冲区,不存在粘包。
这里TCP的服务器和UDP的服务器可以同时使用同一个端口(6000端口),协议不同可以使用同一个端口。一般情况下是不允许两个进程用同一个端口,但是这里若协议不同是可以两个进程用一个端口。

同一个进程,可以创建多个套接字:①创建多个TCP的套接字(明显可以,我们之前用多线程,多进程时创建多个套接字c1,c2…)②创建多个UDP的套接字(也可以)③或者创建多个UDP和TCP的套接字(也可以)。
http
TCP是在传输层
http协议(超文本传输协议,传图片,音乐啥的)80端口在应用层 https协议443端口
http在传输层用的是TCP协议,所以我们等一下写的http服务器,就是之前的TCP网络通讯
在应用层我们要自己写报文了。

当我们输入www.baidu.com(域名)后,浏览器会自动通过DNS解析,就可以得到IP地址,得到IP地址后便可以connect() (IP+80),开始建立连接,进行三次握手,握手完毕后,浏览器会发送http请求报文,告诉服务器我们要干什么,http收到后,回复http应答报文。若在三次握手完成后只进行一次这样的发送请求报文,后发送应答报文,就这样一次操作后便close(),这样的操作叫做“短连接”。若不close(),而是继续发送请求报文,回复应答报文,这样用一个TCP连接,叫做长连接。
什么是长连接和短连接?
在HTTP/1.0中默认使用短连接。也就是说,客户端和服务器每进行一次HTTP操作,就建立一次连接,任务结束就中断连接。当客户端浏览器访问的某个HTML或其他类型的Web页中包含有其他的Web资源(如JavaScript文件、图像文件、CSS文件等),每遇到这样一个Web资源,浏览器就会重新建立一个HTTP会话。
而从HTTP/1.1起,默认使用长连接,用以保持连接特性。在使用长连接的情况下,当一个网页打开完成后,客户端和服务器之间用于传输HTTP数据的TCP连接不会关闭,客户端再次访问这个服务器时,会继续使用这一条已经建立的连接。Keep-Alive不会永久保持连接,它有一个保持时间,可以在不同的服务器软件(如Apache)中设定这个时间。实现长连接需要客户端和服务端都支持长连接。
HTTP协议的长连接和短连接,实质上是TCP协议的长连接和短连接。

当我们用小于1024的端口,要用root模式。
http服务器的实现:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<string.h>
int main(){
int sockfd=socket(AF_INET,SOCK_STREAM,0);
if(sockfd==-1)
exit(1);
struct sockaddr_in saddr;
memset(&saddr,0,sizeof(saddr));
saddr.sin_family=AF_INET;
saddr.sin_port=htons(80);//http 使用80端口
saddr.sin_addr.s_addr=inet_addr("127.0.0.1");
int res=bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
if(res==-1){
printf("bind err\n");
exit(1);
}
listen(sockfd,5);
while(1){
int c=accept(sockfd,NULL,NULL);//这里给NULL,就不要存对方的ip等信息了,我们只要c,accept返回值为新的套接子(后面的通信都是通过这个套接字)\
底层会有对方的ip+port,咱们就不存了,之前咱们就是存了为了看对方的ip罢了,给NULL了后,后面的长度也给NULL
printf("accept c=%d\n",c);
char buff[1024]={0};
int n=recv(c,buff,1023,0);
printf("buff[%d]=%s\n",n,buff);
send(c,"ok",2,0);
close(c);
}
}

运行后,便在浏览器上访问:

我们来看服务器接收到了什么:

我们发了一次请求,却收到了两次请求报文,上面的请求是我们发的,而下面的这个是浏览器帮我们发的。

当我们没有写要访问的资源名,系统会默认为资源名为index.html

浏览器会把我们要访问的文件发过去,服务器接收到了,就要去找该文件。在高性能服务器编程的p75,看完会发现(浏览器打印ok,并不是因为他解析了报文,而是因为我们回复时没有按规格式来,他看不懂便直接打印在上面了)

这样才对。
http有一下请求方法:

get 一般是只读,而post 是提交数据,涉及更新数据
http应答状态码:
200,成功;400 是客户端错误 eg:404,我们浏览器(客户端)进行了请求错误;500是服务器端错误。
这里我们要记住:http请求方法:get ,post的区别
http的应答状态码:200,400,500
我们在浏览器输入域名后会发生什么?
答:先通过DNS域名解析,得到对应的Ip地址,后进行connect(),然后三次握手。。。
咱们就来写一个给浏览器返回文件的服务器,首先我们就要从http请求报文里获取到要访问的文件名。
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<string.h>
#include<fcntl.h>
char * get_filename(char *buff){//来获取文件名
if(buff==NULL)
return NULL;
char * s=strtok(buff," ");
if(s==NULL)
return NULL;
printf("请求方法:%s\n",s);
s=strtok(NULL," ");//通过分割来获取文件名
return s;
}
int main(){
int sockfd=socket(AF_INET,SOCK_STREAM,0);
if(sockfd==-1)
exit(1);
struct sockaddr_in saddr;
memset(&saddr,0,sizeof(saddr));
saddr.sin_family=AF_INET;
saddr.sin_port=htons(80);//http 使用80端口
saddr.sin_addr.s_addr=inet_addr("127.0.0.1");
int res=bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
if(res==-1){
printf("bind err\n");
exit(1);
}
listen(sockfd,5);
while(1){
int c=accept(sockfd,NULL,NULL);//这里给NULL,就不要存对方的ip等信息了,我们只要c,accept返回值为新的套接子(后面的通信都是通过这个套接字)
printf("accept c=%d\n",c);
char buff[1024]={0};
int n=recv(c,buff,1023,0);
printf("buff[%d]=%s\n",n,buff);
char * filename=get_filename(buff);
if(NULL==filename)
{close(c);
continue;
}
char path[256]={"/home/lgq/mycode/day57"};
if(strcmp(filename,"/")==0)//若浏览器没有指定访问资源,就用默认文件名
{
strcat(path,"/index.html");
}
else{
strcat(path,filename);
}
int fd=open(path,O_RDONLY);
if(fd==-1)
{ send(c,"404",3,0);
close(c);
continue;}
int filesize=lseek(fd,0,SEEK_END);//得到文件大小
lseek(fd,0,SEEK_SET);//把文件偏移量移动到开始位置
char head[256]={"HTTP/1.1 200 OK\r\n"};
strcat(head,"Server:myhttp\r\n");
sprintf(head+strlen(head),"Content-Length: %d\r\n",filesize);
strcat(head,"\r\n");
//把报头完成了
send(c,head,strlen(head),0);
int num=0;
int data[1024]={0};
while((num=read(fd,date,1024))>0)
{
send(c,date,num,0);
}
close(fd);
send(c,"ok",2,0);
close(c);
}
这样就OK了
咱们现在要编写index.html文件
<html>
<head>
</head>
<body>
hello world!
</body>
</html>

这样后,便可以用了。
我们也可以在目前位置加入别的文件,eg:图片名 1.webp
咱们直接在浏览器里输入,127.0.0.1/1.webp
就可以打开图片了
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐



所有评论(0)