CSAPP Lab08——Proxy Lab完成思路

蓝色的思念

突然演变成了阳光的夏天

空气中的温暖不会很遥远

——被风吹过的夏天

完整代码见:CSAPP/proxylab-handout at main · SnowLegend-star/CSAPP (github.com)

Q:计算机网络中port的作用是什么?

A:在计算机网络中,"port"(端口)是用于标识网络通信中的特定应用程序或服务的数字标识符。端口是网络通信的重要组成部分,它允许多个应用程序在同一台计算机上共享网络连接,并确保正确地将数据传递给目标应用程序。

端口通常与 IP 地址结合使用,构成了网络通信中的目标地址。IP 地址用于标识网络中的设备,而端口则用于标识设备上的具体应用程序或服务

端口的作用包括:

  1. 标识应用程序或服务:每个应用程序或服务都可以绑定到一个或多个端口上,通过端口号来唯一标识。
  2. 实现多路复用:通过使用端口,多个应用程序可以在同一台计算机上共享网络连接。这意味着同一时间内,可以同时进行多个不同应用程序的通信,每个应用程序使用不同的端口。
  3. 提供通信路由:在网络通信中,端口号帮助路由数据到正确的目标应用程序。当数据包到达目标设备后,根据目标端口号将数据包分发给相应的应用程序或服务。

总之,端口在计算机网络中起着非常重要的作用,它们允许多个应用程序共享网络连接,并确保数据被正确地路由到目标应用程序或服务。


运行课本样例code的方法 

今天突然想把《CSAPP》书上的例子跑一下,结果还是经典的报错。本来以前运行过书上的示例代码,当时就是报错。折腾半天无果索性放弃了。现在想到书都快看完了还没跑成功过示例代码,我心血来潮决定好好研究下到底怎么执行。

想不到还不好搜,找半天才看到一个可行的方案,原来编译书上的代码时都得和csapp.c进行联合编译。因为好多函数都在csapp.c里面重写了,如果不带上这个文件就会有各种报错,看似是没引入头文件导致库函数无法使用,实则是因为当前文件用到的函数其实是在csapp.c内部重新实现了的,只不过csapp.c内部编写的函数命名方式和库函数神似,有点以假乱真。

至此,终于是成功运行了书上的示例代码。


 

一不小心又在proxy lab上磨了好久,主要是一开始对怎么测试proxy感到很疑惑,就去b站上搜了下,结果发现了个讲得很好的up。遂跟着这个up把10~12章重新温习了一番,收获颇多。由于up主的github上只有通过最后一次实验的记录,我又研究半天怎么获得github的历史提交记录,最后也是成功掌握了这个技巧。

言归正传,下面讲讲我完成这个lab的历程。

首先,总体思路图大致如下

 

1、Proxy先与Client进行通信,建立连接并接收并分析来自Client的http-request

2、Proxy把处理好的http-request发送给Server

3、Server发送response给Proxy

4、Proxy将response转发给Client

Part I: Implementing a sequential web proxy

第一部分要求我们实现一个简单的sequential proxy,类似的代码在书上随处可见,tiny.c就是一个很好的模仿对象。它的大致流程如下:

1、在main()里面创建一个监听描述符listenfd,然后在while的循环体里不断尝试与客户端进行连接connfd=Accept(listenfd)。连接成功后将connfd传入请求处理函数handleRequest()中。

2、进入handRequest()后,分别创建好两个I/O缓冲区rio_Proxy2Client和rio_Proxy2Server,Proxy可以利用这两个不同的缓冲区分别和Client与Server进行读写操作。

GET http://www.cmu.edu:8080/hub/index.html HTTP/1.1

Host: www.cmu.edu

User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:10.0.3) Gecko/20120305 Firefox/10.0.3

Connection: close

Proxy-Connection: close

3、利用rio_Proxy2Client读入http-request,并把这个请求分割成writeup要求的几部分,即method、url、version;其中url又可以分割为hostName、port和fielName。

4、处理完request的第一行后,开始处理后序的四个request header,少哪个header就自己补上哪个header。

5、 Proxy利用刚才得到的hostName和port,调用clientfd=Open_clientfd()冒充一个Client与Server建立连接。

6、Proxy利用clientfd和rio_Proxy2Server,先把从Client处得到的http-request发送给Server,然后不断读入Server回复的response。Proxy在读入response的同时,又利用connfd把它们发送给Client。

7、还得加上一个如果method不是“GET”的错误处理函数,直接照着书上的敲一遍或者把tiny.c的那个clientError()复制过来就行。

第一部分的代码难度并不高,核心操作就是要对字符串进行各种处理。

part I的实现如下
 

#include <stdio.h>
#include"csapp.h"
#include"sbuf.h"/* Recommended max cache and object sizes */
#define MAX_CACHE_SIZE 1049000
#define MAX_OBJECT_SIZE 102400
#define HTTP_PREFIX "http://"/* You won't lose style points for including this long line in your code */
static const char *user_agent_hdr = "User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:10.0.3) Gecko/20120305 Firefox/10.0.3\r\n";void handleRequest(int fd);
void parseRequest(char* buf, char* host, char* port, char* method, char* url, char* version, char* fileName);
void clientError(int fd, char* cause, char* errnum, char* errmsg, char* errmsg_datail);
int readAndFormatRequestHeader(rio_t* rio, char* clientRequest, char* Host, char* port,char* method, char* url, char* version, char* filename);//和tiny.c里面的那个差不多
void clientError(int fd, char* cause, char* errnum, char* errmsg, char* errmsg_datail){char buf[MAXLINE];//打印HTTP的响应头sprintf(buf, "HTTP/1.0 %s %s\r\n", errnum, errmsg);Rio_writen(fd, buf, strlen(buf));sprintf(buf, "Content-type: text/html\r\n\r\n");Rio_writen(fd, buf, strlen(buf));//打印HTTP响应的主体sprintf(buf, "<html><title>Tiny Error</title>");Rio_writen(fd,buf,strlen(buf));sprintf(buf, "<body bgcolor=""ffffff"">\r\n");Rio_writen(fd,buf,strlen(buf));sprintf(buf, "%s: %s\r\n", errmsg, errmsg);Rio_writen(fd, buf, strlen(buf));sprintf(buf, "<p>%s: %s\r\n", errmsg_datail, cause);Rio_writen(fd, buf, strlen(buf));sprintf(buf, "<hr><em>The Tiny Web Server</em>\r\n");Rio_writen(fd, buf, strlen(buf));
}void handleRequest(int fd){//处理各种定位的指针char* pos=NULL;//分割http请求的参数char buf[MAXLINE],method[MAXLINE],uri[MAXLINE],version[MAXLINE],fileName[MAXLINE];//客户端的几个请求头的主体char clientRequest[MAXLINE];char hostName[MAXLINE],port[MAXLINE];//proxy-client和proxy-server的IOrio_t rio_Proxy2Client,rio_Proxy2Server;//Step1: proxy读入来自client的请求Rio_readinitb(&rio_Proxy2Client,fd);if(Rio_readlineb(&rio_Proxy2Client,buf,MAXLINE)==0){//这个请求是空的printf("Oops! empty request\n");return ;}//如果http的版本是1.1,处理成1.0if((pos=strstr(buf,"HTTP/1.1"))!=NULL){buf[pos-buf-1+strlen("HTTP/1.1")]='0';}//Step2: 分割请求parseRequest(buf, hostName, port, method, uri, version, fileName);//判断请求是否有效if(strcasecmp(method,"GET")!=0){clientError(fd, method, "501", "Not Implement", "Tiny Does not implement this method");return ;}int rv=readAndFormatRequestHeader(&rio_Proxy2Client, clientRequest, hostName, port, method, uri, version, fileName);if(rv==0){return ;}//Step3: tiny server和proxy建立连接int clientfd=Open_clientfd(hostName, port);Rio_readinitb(&rio_Proxy2Server, clientfd);Rio_writen(rio_Proxy2Server.rio_fd, clientRequest, strlen(clientRequest));//Step4: 从tiny server读入response,并且把它发送给clientprintf("The Proxy is ready to relay the response\n");char tinyResponse[MAXLINE];int n;while((n=Rio_readlineb(&rio_Proxy2Server, tinyResponse, MAXLINE))!=0){Rio_writen(fd, tinyResponse, n);}
}//client request is like this//GET http://www.cmu.edu/hub/index.html HTTP/1.1
void parseRequest(char* buf, char* host, char* port, char* method, char* url, char* version, char* fileName){sscanf(buf,"%s %s %s", method, url, version);//method = "GET", url = "http://localhost:15213/home.html", version = "HTTP1.0"char* host_pos =strstr(url,HTTP_PREFIX)+strlen(HTTP_PREFIX);    //主机名开始的位置char* port_pos =strstr(host_pos,":");                           //端口开始的位置char* slash_pos=strstr(host_pos,"/");                           //suffix开始的位置//判断url有没有带端口号,如果没带就是默认端口80if(port_pos==NULL){ //没带端口号strcpy(port,"80");strncmp(host,host_pos,slash_pos-host_pos);      }else{strncpy(host,host_pos,port_pos-host_pos);strncpy(port,port_pos+1,slash_pos-port_pos-1);}strcpy(fileName,slash_pos);printf("HostName: %s",host);printf("Port: %s",port);printf("fileName: %s",fileName);
}int readAndFormatRequestHeader(rio_t* rio, char* clientRequest, char* Host, char* port,char* method, char* url, char* version, char* fileName){int UserAgent=0, Connection=0, proxyConnection=0, hostInfo=0;char buf[MAXLINE/2];int n;char* findpos;sprintf(clientRequest, "GET %s HTTP/1.0\r\n",fileName);n=Rio_readlineb(rio, buf, MAXLINE);printf("receive buf %s\n", buf);printf("n = %d", n);while(strcmp("\r\n",buf)!=0&&n!=0){strcat(clientRequest, buf);printf("receive buf %s\n", buf);//判断要求的四个请求头是否存在if((findpos=strstr(buf, "User-Agent:"))!=NULL){UserAgent=1;}if((findpos=strstr(buf,"Proxy-Connection:"))!=NULL){proxyConnection=1;}if((findpos=strstr(buf,"Connection"))!=NULL){Connection=1;}if((findpos=strstr(buf, "Host"))!=NULL){hostInfo=1;}n=Rio_readlineb(rio, buf ,MAXLINE);}if(n==0){return 0;}//如果缺失了这四个头部,则进行添加    if(hostInfo==0){sprintf(buf, "Host: %s\r\n", Host);strcat(clientRequest, buf);}if(UserAgent==0){strcat(clientRequest, user_agent_hdr);}if(Connection==0){sprintf(buf, "Connection: close\r\n");strcat(clientRequest, buf);}if(proxyConnection==0){sprintf(buf, "Proxy-Connection: close\r\n");strcat(clientRequest, buf);}//添加最后的空行strcat(clientRequest,"\r\n");return 1;
}int main(int argc,char** argv){if(argc!=2){unix_error("proxy usage: ./proxy <port>");}int listenfd=Open_listenfd(argv[1]);struct sockaddr_storage clientaddr;char hostName[MAXLINE], port[MAXLINE];while(1){socklen_t clientlen=sizeof(struct sockaddr_storage);int connfd=Accept(listenfd,(SA*) &clientaddr,&clientlen);Getnameinfo((SA*) &clientaddr,clientlen,hostName,MAXLINE,port,MAXLINE,0);handleRequest(connfd);Close(connfd);}return 0;
}

Part II

书上给出了三种并发编程的方式:

1、基于进程

2、基于I/O多路复用

3、基于线程

不得不说基于线程是最为通俗易懂的,而且书上还有一个基于线程进行并发编程的例子“echoservert-pre.c”。结合这份代码,在Part I的基础上稍作改动即可。

对了,书上还要求在main()函数里面加入Signal(SIGPIPE, SIG_IGN)来屏蔽SIGPIPE信号。我一开始并没有加这个顺利通过了lab,(:

part II实现如下
 

#include <stdio.h>
#include"csapp.h"
#include"sbuf.h"/* Recommended max cache and object sizes */
#define MAX_CACHE_SIZE 1049000
#define MAX_OBJECT_SIZE 102400
#define HTTP_PREFIX "http://"
#define NTHREADS 4
#define SBUFSIZE 16sbuf_t sbuf;    //shared buffer of connected descriptor
void *thread(void *vargp);/* You won't lose style points for including this long line in your code */
static const char *user_agent_hdr = "User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:10.0.3) Gecko/20120305 Firefox/10.0.3\r\n";void handleRequest(int fd);
void parseRequest(char* buf, char* host, char* port, char* method, char* url, char* version, char* fileName);
void clientError(int fd, char* cause, char* errnum, char* errmsg, char* errmsg_datail);
int readAndFormatRequestHeader(rio_t* rio, char* clientRequest, char* Host, char* port,char* method, char* url, char* version, char* filename);//和tiny.c里面的那个差不多
void clientError(int fd, char* cause, char* errnum, char* errmsg, char* errmsg_datail){char buf[MAXLINE];//打印HTTP的响应头sprintf(buf, "HTTP/1.0 %s %s\r\n", errnum, errmsg);Rio_writen(fd, buf, strlen(buf));sprintf(buf, "Content-type: text/html\r\n\r\n");Rio_writen(fd, buf, strlen(buf));//打印HTTP响应的主体sprintf(buf, "<html><title>Tiny Error</title>");Rio_writen(fd,buf,strlen(buf));sprintf(buf, "<body bgcolor=""ffffff"">\r\n");Rio_writen(fd,buf,strlen(buf));sprintf(buf, "%s: %s\r\n", errmsg, errmsg);Rio_writen(fd, buf, strlen(buf));sprintf(buf, "<p>%s: %s\r\n", errmsg_datail, cause);Rio_writen(fd, buf, strlen(buf));sprintf(buf, "<hr><em>The Tiny Web Server</em>\r\n");Rio_writen(fd, buf, strlen(buf));
}void handleRequest(int fd){//处理各种定位的指针char* pos=NULL;//分割http请求的参数char buf[MAXLINE],method[MAXLINE],uri[MAXLINE],version[MAXLINE],fileName[MAXLINE];//客户端的几个请求头的主体char clientRequest[MAXLINE];char hostName[MAXLINE],port[MAXLINE];//proxy-client和proxy-server的IOrio_t rio_Proxy2Client,rio_Proxy2Server;//Step1: proxy读入来自client的请求Rio_readinitb(&rio_Proxy2Client,fd);if(Rio_readlineb(&rio_Proxy2Client,buf,MAXLINE)==0){//这个请求是空的printf("Oops! empty request\n");return ;}//如果http的版本是1.1,处理成1.0if((pos=strstr(buf,"HTTP/1.1"))!=NULL){buf[pos-buf-1+strlen("HTTP/1.1")]='0';}//Step2: 分割请求parseRequest(buf, hostName, port, method, uri, version, fileName);//判断请求是否有效if(strcasecmp(method,"GET")!=0){clientError(fd, method, "501", "Not Implement", "Tiny Does not implement this method");return ;}int rv=readAndFormatRequestHeader(&rio_Proxy2Client, clientRequest, hostName, port, method, uri, version, fileName);if(rv==0){return ;}//Step3: tiny server和proxy建立连接int clientfd=Open_clientfd(hostName, port);Rio_readinitb(&rio_Proxy2Server, clientfd);Rio_writen(rio_Proxy2Server.rio_fd, clientRequest, strlen(clientRequest));//Step4: 从tiny server读入response,并且把它发送给clientprintf("The Proxy is ready to relay the response\n");char tinyResponse[MAXLINE];int n;while((n=Rio_readlineb(&rio_Proxy2Server, tinyResponse, MAXLINE))!=0){Rio_writen(fd, tinyResponse, n);}
}//client request is like this//GET http://www.cmu.edu/hub/index.html HTTP/1.1
void parseRequest(char* buf, char* host, char* port, char* method, char* url, char* version, char* fileName){sscanf(buf,"%s %s %s", method, url, version);//method = "GET", url = "http://localhost:15213/home.html", version = "HTTP1.0"char* host_pos =strstr(url,HTTP_PREFIX)+strlen(HTTP_PREFIX);    //主机名开始的位置char* port_pos =strstr(host_pos,":");                           //端口开始的位置char* slash_pos=strstr(host_pos,"/");                           //suffix开始的位置//判断url有没有带端口号,如果没带就是默认端口80if(port_pos==NULL){ //没带端口号strcpy(port,"80");strncmp(host,host_pos,slash_pos-host_pos);      }else{strncpy(host,host_pos,port_pos-host_pos);strncpy(port,port_pos+1,slash_pos-port_pos-1);}strcpy(fileName,slash_pos);printf("HostName: %s",host);printf("Port: %s",port);printf("fileName: %s",fileName);
}int readAndFormatRequestHeader(rio_t* rio, char* clientRequest, char* Host, char* port,char* method, char* url, char* version, char* fileName){int UserAgent=0, Connection=0, proxyConnection=0, hostInfo=0;char buf[MAXLINE/2];int n;char* findpos;sprintf(clientRequest, "GET %s HTTP/1.0\r\n",fileName);n=Rio_readlineb(rio, buf, MAXLINE);printf("receive buf %s\n", buf);printf("n = %d", n);while(strcmp("\r\n",buf)!=0&&n!=0){strcat(clientRequest, buf);printf("receive buf %s\n", buf);//判断要求的四个请求头是否存在if((findpos=strstr(buf, "User-Agent:"))!=NULL){UserAgent=1;}if((findpos=strstr(buf,"Proxy-Connection:"))!=NULL){proxyConnection=1;}if((findpos=strstr(buf,"Connection"))!=NULL){Connection=1;}if((findpos=strstr(buf, "Host"))!=NULL){hostInfo=1;}n=Rio_readlineb(rio, buf ,MAXLINE);}if(n==0){return 0;}//如果缺失了这四个头部,则进行添加    if(hostInfo==0){sprintf(buf, "Host: %s\r\n", Host);strcat(clientRequest, buf);}if(UserAgent==0){strcat(clientRequest, user_agent_hdr);}if(Connection==0){sprintf(buf, "Connection: close\r\n");strcat(clientRequest, buf);}if(proxyConnection==0){sprintf(buf, "Proxy-Connection: close\r\n");strcat(clientRequest, buf);}//添加最后的空行strcat(clientRequest,"\r\n");return 1;
}int main(int argc,char** argv){if(argc!=2){unix_error("proxy usage: ./proxy <port>");}int listenfd=Open_listenfd(argv[1]), i;pthread_t tid;struct sockaddr_storage clientaddr;char hostName[MAXLINE], port[MAXLINE];sbuf_init(&sbuf, SBUFSIZE);for(i=0;i<NTHREADS;i++){    //创建工作线程Pthread_create(&tid, NULL, thread, NULL);}while(1){socklen_t clientlen=sizeof(struct sockaddr_storage);int connfd=Accept(listenfd,(SA*) &clientaddr,&clientlen);//Getnameinfo((SA*)&clientaddr, clientlen, hostName, MAXLINE, port, MAXLINE, 0);sbuf_insert(&sbuf, connfd);}return 0;
}void *thread(void *vargp){pthread_detach(pthread_self());while(1){int connfd=sbuf_remove(&sbuf);handleRequest(connfd);Close(connfd);}
}

 

Part III

最后一部分写的我汗流浃背了。

这一部分要求我们给Proxy添加cache。对于cache的读取方式,可以参照第一类“读者-写者”问题。我一开始也并没有反应过来可以在此引入“读者-写者”模式,还是在网上参考了其他人的解决方案才隐约发现得这么做的哈哈哈。

其实可以直接把和cache相关的代码写在proxy.c里面,就是会让proxy.c更为臃肿一点。但我一想书上都给出sbuf.c和sbuf.h了,我也模仿着写一个cache.c和cache.h岂不美哉?正式这个决定让我后序改bug改得一头大包。我先在proxy.c中定义了缓存cache_t cache,然后进行参数传递在cache.c中修改cache。由于我对全局变量跨文件的处理方式一知半解,但还是硬着头皮写了下去。

后面仔细思考了下,关于“全局变量跨文件的处理”其实有两种方案。

       方案一:我直接cache_t cache写入cache.h当中,而不是写在proxy.c。这样只需要在cache.c和proxy.c中直接引入“cache.h”就可以直接用cache了,但是这样有个弊端———有且仅有一个cache供proxy.c使用,如果还有个proxy2.c也要用到cache,那就没得用了。还是治标不治本。

       方案二:用指针传递来在cahce.c中修改proxy.c定义的cache_t cache。这方法其实不难,我最后也是采用的这个方法。坏就坏在昨晚写的代码简直就是一坨,好像用了指针传递又好像没用,我就在这一坨东西上反复修改,然后越陷越深。理清了思路之后问题就迎刃而解了。

还有几个让我困惑的小问题:

①malloc问题

void cache_init(cache_t *cache) {// cache=(cache_t*)Malloc(sizeof(cache_t));cache->cache_Item_Using = 0;
}

在这里我开始准备模仿sbuf.c给cache分配个空间再说,结果测试的时候一直报错“Segmentation fault”,搞得我一度以为是malloc出了问题。最后把参数传递的问题解决后发现这里的malloc可用可不用,由于cache_t cache是全局变量,它的生命周期和进行同步,故没必要多此一举再在堆上给它分配空间了。

②url问题

     printf("=====The length of url is: %d====\n",(int)sizeof(url));// strncpy(item->url,url,sizeof(url));  西八,这个bug害得我好苦啊strncpy(item->url,url,MAXLINE);

在解决缓存的问题后,我发现cache部分的得分依然是0昏。初步判断是url的匹配有问题,测试了一番后发现一个十分怪异的情况——存入的url长度并不能用sizeof(url),实际长度好像比sizeof(url)还要长。

 

 

把每次存进cache的item->url打印出来一看果然如此,于是最后直接假设url的长度是MAXLINE,这样就可以通过测试了

 

Q:例如char* url=“http://localhost:7710/”

那sizeof(url)是输出8还是字符串本身的长度22呢?

A:    在这种情况下,char* url = "http://localhost:7710/"中的url是一个指向字符的指针,指向字符串常量"http://localhost:7710/"的首地址。因此,使用sizeof(url)将返回指针的大小,而不是字符串的长度。

在大多数平台上,指针的大小通常是机器字长的大小。在64位系统上,指针通常是8个字节,因此sizeof(url)将返回8。

要获取字符串本身的长度,您可以使用strlen函数,如下所示:

#include <stdio.h>#include <string.h>int main() {char *url = "http://localhost:7710/";size_t length = strlen(url);printf("Length of the string: %zu\n", length); // 这里将输出字符串的长度,即22return 0;}

这段代码将输出字符串的长度,即22。

但是,由于strlen(str)不会包括结尾的‘\0’,所以得用strncpy(item->url,url,strlen(url)+1)

至此,真相大白。 

csche.c如下
 

#include "csapp.h"
#include "cache.h"void initializeCache(cache_t* cache){cache->head = Malloc(sizeof(*(cache->head)));cache->head->flag = '@';cache->head->prev = NULL;cache->head->next = NULL;cache->tail = Malloc(sizeof(*(cache->tail)));cache->tail->flag = '@';cache->tail->prev = NULL;cache->tail->next = NULL;/* construct the doubly linked list */cache->head->next = cache->tail;cache->tail->prev = cache->head;cache->nitems = 0;
}

cache.h实现如下

#include "csapp.h"
#define MAX_CACHE_SIZE 1049000
#define MAX_OBJECT_SIZE 102400typedef struct _obj_t{char flag;char uri[100];char respHeader[1024];char respBody[MAX_OBJECT_SIZE];int respHeaderLen;int respBodyLen;struct _obj_t* prev;struct _obj_t* next;
}obj_t;typedef struct _cache_t{obj_t* head;obj_t* tail;    int nitems;
}cache_t;//write to cache
//read cache
//search cachevoid initializeCache(cache_t* );

写入到cache的实现如下

/** This function is guarded by Write Lock, thus is thread safe* assume head is the newest part, we evict the last part* if possible*/
void writeToCache(obj_t* obj){/* step1: check current capacity, if full ,delete one */while(obj->respBodyLen + cacheSize > MAX_CACHE_SIZE && cache.head->next != cache.tail){obj_t* last = cache.tail->prev;last->next->prev = last->prev;last->prev->next = last->next;last->next = NULL;last->prev = NULL;Free(last);}/* step2: add into the cache *///mount the current obj into cacheobj->next = cache.head->next;obj->prev = cache.head;cache.head->next->prev = obj;cache.head->next       = obj;cacheSize += obj->respBodyLen;
}

 

 读取cahce条目的实现如下

obj_t* readItem(char* targetURI, int clientfd){P(&mutex);readcnt++;if(readcnt == 1){P(&W);}V(&mutex);/***** reading section starts *****/obj_t* cur = cache.head->next;rio_t rio;Rio_readinitb(&rio, clientfd);while(cur->flag != '@'){if(strcmp(targetURI, cur->uri) == 0){return cur;}cur = cur->next;}/***** reading section ends *****/P(&mutex);readcnt--;if(readcnt == 0){V(&W);}V(&mutex);return NULL;
}

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/845811.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

qt中实现多语言功能

qt中实现多语言功能 原理&#xff1a; 其本质就是生成ts文件&#xff0c;然后使用Linguist软件手工翻译&#xff0c;再生成qm文件&#xff0c;最后在主程序的开始加载不同的qm文件&#xff0c;实现多语言。 步骤&#xff1a; 修改程序文件 在pro文件中加入说明 TRANSLATI…

Socket网络通讯入门(一)

提示&#xff1a;能力有限&#xff0c;不足以及错误之处还请指出&#xff01; 文章目录 前言一、 计算机网络 OSI、TCP/IP、五层协议 体系结构1.OSI七层模型每层的作用2.TCP/IP协议分成3.五层协议体系结构 二、Socket服务端和客户端 简单通信1.服务端代码2.客户端 总结 前言 简…

vs - 在win10中安装vs2013update5

文章目录 vs - 在win10中安装vs2013update5概述笔记直接安装vs2013-update5报错先安装vs2013原版安装 vs2013 update5测试备注END vs - 在win10中安装vs2013update5 概述 用VS2019写的程序&#xff0c;在早期windows(e.g. win7, win8.1)上安装时&#xff0c;需要UCRT。 UCRT是…

SpringBoot整合jasypt加密配置文件敏感信息

SpringBoot整合jasypt加密配置文件敏感信息 在项目中我们需要对配置文件的一些敏感信息进行加密处理&#xff0c;比如数据库账户密码&#xff0c;避免直接暴露出来&#xff0c;这种场景常常用于生产环境&#xff0c;我们不想让开发人员知道生产库的密码&#xff0c;有运维人员…

基础篇01——SQL的基本语法和分类

MySQL数据库安装与基本使用 安装教程参见&#xff1a;通过zip安装MySQL 通过命令行启动和停止MySQL服务命令 前提&#xff1a;安装MySQL成功之后 启动服务&#xff1a;net start mysql 停止服务&#xff1a;net stop mysql 通过命令行连接mysql 可以通过mysql的客户端命令行…

入手戴尔R720,通过iDRAC查看系统信息

想入手服务器很久了&#xff0c;一直担心功耗太高&#xff0c;今天狠心搞了一台戴尔服务器R720。 需求 开虚拟机&#xff0c;核心数要多 学习以下 Windows云桌面AD域控office online serverubuntu试验机 随便折腾不怕玩坏 最好两个网口以上 稳定 四个以上硬盘位 ‍ 为什么是…

在AutoDL上部署百川2大模型

在AutoDL上部署百川2大模型 AUTO DL相关 官网地址 选择容器实例 租用新实例 选择配置 我这里选择3090 , 硬盘尽量选择可以扩容的 , CUDA版本尽量高一点 选择镜像 先将机器关机 先扩容一下数据盘 50G就可以了 然后选择无卡模式开机 因为无卡模式下开机费用会变低 使用JupyterL…

1.3Java对象和类

Java作为一种面向对象语言。支持以下基本概念&#xff1a; 多态继承封装抽象类对象实例方法重载 本节我们重点研究对象和类的概念。 对象&#xff1a;对象是类的一个实例&#xff08;对象不是找个女朋友&#xff09;&#xff0c;有状态和行为。例如&#xff0c;一条狗是一个…

BrainGPT1,一个帮你b站点歌放视频的多模态多轮对话模型

BrainGPT1&#xff0c;一个帮你b站点歌放视频的多模态多轮对话模型 返回论文目录 项目地址 模型地址 作者&#xff1a;华东师范大学&#xff0c;计算机科学与技术学院&#xff0c;智能教育研究院的小怪兽会微笑。 介绍 BrainGPT1是一个工具调用多轮对话模型&#xff0c;与G…

免费实现网站HTTPS访问

HTTPS&#xff08;Hypertext Transfer Protocol Secure&#xff09;是一种基于SSL协议的HTTP安全协议&#xff0c;旨在为客户端&#xff08;浏览器&#xff09;与服务器之间的通信提供加密通道&#xff0c;确保数据在传输过程中的保密性、完整性和身份验证。与传统的HTTP相比&a…

【数据结构与算法(C 语言)】栈的基本操作函数(动图演示) 及 栈的实际应用之一:进制转换

目录 1. 前言2. 结构及基本操作函数&#xff1a;2.1 栈的结构类型 Stack2.2 初始化栈 InitStack2.3 销毁栈 DestroyStack2.4 清空栈 ClearStack2.5 判断栈是否为空 StackEmpty2.6 获取stack的长度 StackLength2.7 获取栈顶元素 GetTop2.8 入栈 Push2.9 出栈 Pop2.10 访问元素2.…

COLING 2024: 复旦发布AoR,层级聚合推理突破大模型复杂推理上限

“三个臭皮匠&#xff0c;顶个诸葛亮&#xff1f;” “一个模型不行&#xff0c;那就再堆一个&#xff1f;” 过去当我们在处理复杂任务的时候&#xff0c;往往会考虑集成策略&#xff08;Ensembling Strategy&#xff09;&#xff0c;通过多个模型投票的方式&#xff0c;选出…

图形学初识--屏幕空间变换

文章目录 前言正文为什么需要屏幕空间变换&#xff1f;什么是屏幕空间变换&#xff1f;屏幕空间变换矩阵如何推导&#xff1f;问题描述步骤描述 结尾&#xff1a;喜欢的小伙伴点点关注赞哦! 前言 前面章节主要讲解了视图变换和投影变换&#xff0c;此时距离在屏幕空间显示也就…

alexnet模型_图像分类算法对动物有无斑纹识别-不含数据集图片-含逐行注释和说明文档

alexnet模型_图像分类算法对动物有无斑纹识别-不含数据集图片-含逐行注释和说明文档 代码下载地址&#xff1a; https://download.csdn.net/download/qq_34904125/89357170 本代码是基于python pytorch环境安装的。 下载本代码后&#xff0c;有个环境安装的requirement.txt…

广告变现是什么

广告变现是指媒体或平台通过向用户展示广告主的广告&#xff0c;从而获得收入的过程。 广告变现就像是一个店主&#xff0c;他需要有一个吸引人的店面&#xff0c;提供优质的内容和服务&#xff0c;然后在店里摆放一些别人的商品或服务&#xff0c;每当有客人看了或买了这…

量化研究---强大的可转债分析系统上线,提供api,实时数据支持

今天把可转债实盘的分析模型拿出来&#xff0c;放在服务器方便选股分析&#xff0c;方便后面对接大qmt直接选股交易 强大的禄得可转债自定义因子轮动系统完成&#xff0c;可转债三低为例子 自定义因子实盘的框架 自定义因子轮动框架非常强大 网页 http://120.78.132.143:8023/…

python上位机串行通信接收字节数据的校验处理-以crc16-modbus为例

在串行通信中&#xff0c;接收到的数据是否正确&#xff0c;一般用CRC校码的方式来完成。上位机向下位机发送数据时&#xff0c;需要加上校验码&#xff0c;同理&#xff0c;下位机向上位机上报数据时&#xff0c;也需要加上校验码。 校验码的计算方法有很多&#xff0c;比较简…

C# WinForm —— 23 Timers.Timer 组件介绍与使用

1. 简介 System.Timers.Timer 计时器 轻量 每隔一段时间触发Elapsed事件&#xff0c;执行操作(不是由UI线程执行的)&#xff0c;即使事件中执行了比较耗时的操作&#xff0c;也不会造成 UI 失去响应 如果要获取服务器的计时功能的话&#xff0c;可以使用System.Timers.Timer …

轻松记录收支明细,智能筛选并统计买菜历史记录:一键掌握家庭财务

在繁忙的生活中&#xff0c;你是否曾为家庭财务的琐碎而烦恼&#xff1f;买菜、购物、日常开销……每一笔支出都似乎难以捉摸&#xff0c;让你在月底对账时头疼不已。现在&#xff0c;我们为你带来了一款全新的财务记录工具&#xff0c;让你轻松记录收支明细&#xff0c;智能筛…

关于如何在Arch Linux上编写自己的第一个module

前一段时间一直想深入学习编写一个module插入到自己的内核当中&#xff0c;但是网上的资料基本上全都针对的Ubuntu和Debian等流行的Linux发行版&#xff0c;这里打算简单的记录一波博客。 啥是Module?(着急可不看) 众所周知&#xff1a;现代宏内核架构的操作系统都会借鉴微内核…