网络交互和数据传输好比打电话,socket就像电话机,是在网络编程世界中与外界进行网络通信的途径

TCP网络编程

基于服务器-客户端模型,使用套接字完成连接的建立

服务端准备连接

使用socket创建一个可用的套接字:

NAME
socket - create an endpoint for communication SYNOPSIS
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h> int socket(int domain, int type, int protocol);

domain表示套接字域,type参数设定为SOCK_STREAM就表示为字节流,对应于TCP,protocol参数指定为0

创建套接字后要将其和套接字和套接字和地址绑定:

NAME
bind - bind a name to a socket SYNOPSIS
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h> int bind(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);

函数的地一个参数是上述的套接字文件描述符,第二个参数是通用地址格式sockaddr,第三个参数是地址长度

设置bind的参数时,对地址和端口有多种处理方式,可以将设置成本机IP地址,这相当于告诉操作系统内核,仅仅对目标IP是本机IP地址的IP包进行处理。但是将程序部署时有一个问题: 开发者并不清楚程序将会被部署到哪一台机器上。此时设置通配地址,对于IPv4地址来说,使用INADDR_ANY配置通配地址,IPv6则是IN6ADDR_ANY完成设置

如下函数创建了一个套接字绑定地址和端口号并返回:

int create_socket(uint16_t port)
{
int sock;
struct sockaddr_in addr; // create socket
sock = socket(AF_INET,SOCK_STREAM,0);
if (sock < 0) {
perror("socket");
exit(EXIT_FAILURE);
} addr.sin_family = AF_INET;
addr.sin_port = htons(port); // 主机字节序和网络字节序的转换
addr.sin_addr.s_addr = htonl(INADDR_ANY);
// bind address:port
if (bind(sock,(struct sockaddr *)&addr,sizeof(addr)) < 0) {
perror("bind");
exit(EXIT_FAILURE);
} return sock;
}

监听

使用listen函数等待用户请求,操作系统会为此做好接收用户全球的一切准备,比如完成准备队列

NAME
listen - listen for connections on a socket SYNOPSIS
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h> int listen(int sockfd, int backlog);

第一个参数是sockfd为套接字描述符,第二个参数backlog决定了可以接收的并发数目,这个参数越大,并发数目理论上也会越大

应答

当客户端的连接请求到达时,服务器端应答成功,建立连接,accept函数看成是操作系统内核和应用程序之间的桥梁

NAME
accept, accept4 - accept a connection on a socket SYNOPSIS
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h> int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

第一个参数sockfd是套接字,是前面通过bind和listen一系列操作得到的套接字,该函数的返回值有两个部分,第一个部分是addr通过指针方式获取的客户端的地址,addrlen显示地址的大小,第二个是返回已连接套接字描述符

这里将监听套接字和已连接套接字分开,因为网络程序的并发特征。监听套接字是一直都存在的,直到这个套接字关闭,而一旦一个客户与服务器连接成功,完成了TCP三次握手,操作系统内核就为这个客户生成一个已连接套接字,让应用服务器使用这个已连接套接字和客户进行通信处理。如果客户关闭连接,那么释放的是已连接套接字,这样就完成了TCP的释放。监听套接字依然还处于"监听"状态

TCP服务端代码

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h> int create_socket(uint16_t port)
{
int sock;
struct sockaddr_in addr; // create socket
sock = socket(AF_INET,SOCK_STREAM,0);
if (sock < 0) {
perror("socket");
exit(EXIT_FAILURE);
} addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = htonl(INADDR_ANY);
// bind address:port
if (bind(sock,(struct sockaddr *)&addr,sizeof(addr)) < 0) {
perror("bind");
exit(EXIT_FAILURE);
} return sock;
} int main(void)
{
int serv_sock = create_socket(8090);
if (listen(serv_sock,5) < 0) {
perror("listen");
exit(EXIT_FAILURE);
} printf("listening at port 8090...\n"); while (true) {
struct sockaddr_in clnt_addr = {0};
socklen_t len = sizeof(clnt_addr);
int clnt_sock = accept(serv_sock,(struct sockaddr*)&clnt_addr,&len);
if (clnt_sock < 0) {
perror("connect");
continue;
} else {
printf("%s:%d is connecting\n",inet_ntoa(clnt_addr.sin_addr),ntohs(clnt_addr.sin_port));
}
close(clnt_sock);
} close(serv_sock); return 0;
}

inet_ntoa的作用是类型转换,ntohs将网络字节序转化为主机字节序

客户端发起连接

客户端同样须要建立一个套接字,方法是一样的,但客户端是通过connect函数发起请求

NAME
connect - initiate a connection on a socket SYNOPSIS
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h> int connect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);

第一个参数是sockfd是连接套接字,通过socket创建。第二个和第三个参数代表指向套接字地址结构的指针和该结构的大小,套接字地址结构必须含有服务器的IP地址和端口号,客户端在调用函数connect前不必调用bind函数,因为如果需要的话,内核会确定IP地址,并自行确定端口号

对于TCP套接字,connect函数将触发TCP的3次握手过程,出错返回可能有以下几种情况:

  1. 三次握手无法建立,客户端发出的SYN包没有任何响应,返回TIMEOUT错误
  2. 客户端收到RST复位应答,这时候客户端会立即返回CONNECTION REFUSED错误,这种情况比较常见于客户端发送连接请求时的请求端口写错,因为 RST 是 TCP 在发生错误时发送的一种 TCP 分节,产生RST的3个条件是:目的地为某端口的 SYN 到达,然而该端口上没有正在监听的服务;TCP 想取消一个已有连接;TCP 接收到一个根本不存在的连接上的分节
  3. 客户发出的 SYN 包在网络上引起了"destination unreachable",即目的不可达的错误。这种情况比较常见的原因是客户端和服务器端路由不通

客户端代码

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h> int main(void)
{
int clnt_sock = socket(PF_INET,SOCK_STREAM,0);
if (clnt_sock < 0) {
perror("socket");
exit(EXIT_FAILURE);
} struct sockaddr_in serv_addr;
inet_pton(AF_INET,"127.0.0.1",&serv_addr.sin_addr);
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(8090); if (connect(clnt_sock,(struct sockaddr*)&serv_addr,sizeof(serv_addr)) < 0) {
perror("connect");
exit(EXIT_FAILURE);
} printf("connect succeed\n");
close(clnt_sock);
return 0;
}

运行测试

开启服务器:

$ gcc tcp_server.c -o tcp_server && ./tcp_server
listening at port 8090...

运行客户端:

$ gcc tcp_client.c -o tcp_client && ./tcp_client
connect succeed

服务端打印信息:

$ gcc tcp_server.c -o tcp_server && ./tcp_server
listening at port 8090...
127.0.0.1:55052 is connecting

wireshakr抓包:

上述7条记录分别对应了3次握手和4次挥手,前3条记录就是3次握手:

3次握手具体的过程:

  1. 客户端协议栈向服务器端发送了SYN包,并告诉服务端当前序列号j,客户端进入SYNC_SENT状态

  1. 服务端的协议栈收到这个包之后,和客户端进入ACK应答,应答的值为j+1,表示对SYN包j的确认,同时服务器也发送一个SYN包,告诉客户端当前发送序列号为k,服务器端进入SYNC_RCVD状态

  1. 客户端协议栈收到ACK之后,使得应用程序从connect调用返回,表示客户端到服务器端的单向连接建立成功,客户端的状态为ESTABLISHED,同时客户端协议栈也会对服务器端的SYN包进行应答,应答数据为k+1

UDP网络编程

UDP是一种"数据报"协议,TCP是一种面向连接的"数据流"协议。TCP在IP报文的基础上增加了诸如重传、确认、有序传输、拥塞控制等能力,通信的双方是在一个确定的上下文中工作的。而UDP没有一个确定的上下文,是一个不可靠的通信协议,没有重传和确认,没有有序控制,也没有拥塞控制。

服务端

服务器端创建 UDP 套接字之后,绑定到本地端口,调用 recvfrom 函数等待客户端的报文发送

服务端代码:

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h> #define SERV_PORT 8090 int main(void)
{
int serv_sock = socket(AF_INET,SOCK_DGRAM,0);
if (serv_sock < 0) {
perror("socket");
exit(EXIT_FAILURE);
} struct sockaddr_in serv_addr = {0};
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_addr.sin_port = htons(SERV_PORT); if (bind(serv_sock,(struct sockaddr*)&serv_addr,sizeof(serv_addr)) < 0) {
perror("bind");
exit(EXIT_FAILURE);
} char message[1024] = {0}; struct sockaddr_in clnt_addr = {0};
socklen_t len = sizeof(clnt_addr);
while (true) {
int n = recvfrom(serv_sock,message,1024,0,(struct sockaddr*)&clnt_addr,&len);
message[n] = 0;
printf("received %d bytes: %s from %s:%u\n",n,message,
inet_ntoa(clnt_addr.sin_addr),ntohs(clnt_addr.sin_port));
strcpy(message,"receive OK");
sendto(serv_sock,message,sizeof(message),
0,(struct sockaddr*)&clnt_addr,len
);
} return 0;
}

如上代码,服务端调用recvfrom接收发送来的数据,调用sento向客户端发送数据

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen); ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);

这两个函数的前4个参数相同,分别是本地创建的套接字描述符,数据缓冲区,缓冲区最大长度,标志,对于recvfrom来说后2个参数用于利用指针获取发送方的地址信息,对于sento来说是接收方的地址信息和长度,表示要发给谁。在服务端程序中,这个地址就是客户端的地址,服务端通过recvfrom函数拿到发送数据的客户端的地址信息,传递给sendto用于发送数据

客户端

读取输入的字符串后,发送给服务端,并且把服务端经过处理的报文打印到标准输出上

代码:

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h> #define SERV_PORT 8090 int main(void)
{
int clnt_sock = socket(AF_INET,SOCK_DGRAM,0);
if (clnt_sock < 0) {
perror("socket");
exit(EXIT_FAILURE);
} struct sockaddr_in serv_addr = {0};
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(SERV_PORT);
serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); socklen_t len = sizeof(serv_addr);
char send_buff[1024] = {0};
char recv_buff[1024] = {0}; while (true) {
scanf("%s",send_buff);
// send message
sendto(clnt_sock,send_buff,sizeof(send_buff),0,
(struct sockaddr*)&serv_addr,len);
// recv message
int ret = recvfrom(clnt_sock,recv_buff,255,0,
(struct sockaddr*)&serv_addr,&len); if (ret > 0) {
recv_buff[ret] = 0;
printf("recv %d bytes:%s from %s:%d\n",ret,recv_buff,
inet_ntoa(serv_addr.sin_addr),ntohs(serv_addr.sin_port));
}
} return 0;
}

运行测试

客户端发送数据:

$ ./udp_client
Hello!
recv 255 bytes:receive OK from 127.0.0.1:8090

服务端接收数据:

$ ./udp_server
received 1024 bytes: Hello! from 127.0.0.1:43968

本地套接字

本地套接字一般也叫做 UNIX 域套接字,本地套接字是 IPC,也就是本地进程间通信的一种实现方式。除了本地套接字以外,其它技术,诸如管道、共享消息队列等也是进程间通信的常用方法,但因为本地套接字开发便捷,接受度高,所以普遍适用于在同一台主机上进程间通信的各种场景。本地套接字是一种特殊类型的套接字,和 TCP/UDP 套接字不同。TCP/UDP 即使在本地地址通信,也要走系统网络协议栈,而本地套接字,严格意义上说提供了一种单主机跨进程间调用的手段,减少了协议栈实现的复杂度,效率比 TCP/UDP 套接字都要高许多。类似的 IPC 机制还有 UNIX 管道、共享内存和 RPC 调用等

本地字节流套接字

服务器打开本地套接字后,接收客户端发送来的字节流,并往客户端回送了新的字节流

服务端

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h> int main(int argc,char *argv[])
{
if (argc != 2) {
error(1,0,"usage: unixstreamserver <local_path>");
} // 监听套接字&连接套接字
int listenfd,connfd;
socklen_t clilen;
struct sockaddr_un cliaddr,servaddr; listenfd = socket(AF_LOCAL,SOCK_STREAM,0); // 套接字类型
if (listenfd < 0) {
error(1,errno,"socket created failed");
} char *local_path = argv[1]; // 地址
unlink(local_path); // 删除路径
bzero(&servaddr,sizeof(servaddr));
servaddr.sun_family = AF_LOCAL;
strcpy(servaddr.sun_path,local_path); // 设置本地文件路径 // 调用bind和listen监听在一个套接字上
if (bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr)) < 0) {
error(1,errno,"bind failed");
} if (listen(listenfd,5) < 0) {
error(1,errno,"listen failed");
} clilen = sizeof(cliaddr);
if ((connfd = accept(listenfd,(struct sockaddr*)&cliaddr,&clilen)) < 0) {
if (errno == EINTR)
error(1,errno,"accept failed");
else
error(1,errno,"accept failed");
} // 读取和发送数据
char recv_buff[1024] = {0};
while (true) {
if (read(connfd,recv_buff,1024) == 0) {
printf("client quit\n");
break;
}
printf("receive: %s",recv_buff);
char send_buff[1024] = {0};
sprintf(send_buff,"Hello, %s",recv_buff); int nbytes = sizeof(send_buff);
if (write(connfd,send_buff,nbytes) != nbytes)
error(1,errno,"write error");
} // 关闭套接字
close(listenfd);
close(connfd); return 0;
}

这段程序首先创建了一个套接字,将类型指定为AF_LOCAL(等价于AF_UNIX),并且使用字节流格式。之后设置sun_path创建一个本地文件路径标识的套接字上(必须是一个文件不能是一个目录),和普通的TCP服务端没有什么区别(将IP地址换成了文件路径)。在此之前调用了一个unlink,如果原先已经存在了相同名称的文件则将其删除。之后与TCP雷同,使用bind和listen来绑定套接字,使用accept来应答连接。之后就可以使用read和write来进行数据读写。

客户端

如下是客户端程序:

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h>
#include <error.h>
#include <errno.h> int main(int argc,char *argv[])
{
if (argc != 2) {
error(1,0,"usage: unixstreamclient <local path>");
} int sockfd;
struct sockaddr_un servaddr = {0};
sockfd = socket(AF_LOCAL,SOCK_STREAM,0);
if (sockfd < 0) {
error(1,errno,"create socket failed");
} servaddr.sun_family = AF_LOCAL;
strcpy(servaddr.sun_path,argv[1]); if (connect(sockfd,(struct sockaddr*)&servaddr,sizeof(servaddr)) < 0) {
error(1,errno,"connect failed");
} char send_buff[1024] = {0};
char recv_buff[1024] = {0}; while (fgets(send_buff,1024,stdin) != NULL) {
int nbytes = sizeof(send_buff);
if (write(sockfd,send_buff,1024) != nbytes) {
error(1,errno,"write error");
}
if (read(sockfd,recv_buff,1024) == 0) {
error(1,errno,"server terminated prematurely");
} printf("server: ");
fputs(send_buff,stdout);
} return 0;
}

首先创建一个套接字,使用字节流类型。之后设置目标服务器地址,即文件路径,随后使用connect进行连接(不会出现3次握手),最后使用read和write进行数据读写

运行测试

服务端:

$ sudo ./local_server /var/lib/unixstream.sock
receive: a
receive: b

客户端:

$ sudo ./local_client /var/lib/unixstream.sock
a
a
b
b

从客户端输入字符,服务端返回相同的字符

本地数据报套接字

在本地套接字上使用数据报

服务端

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h>
#include <error.h>
#include <errno.h> int main(int argc,char *argv[])
{
if (argc != 2) {
error(1,0,"usage: unixdataserver <local path>");
} int socket_fd;
socket_fd = socket(AF_LOCAL,SOCK_DGRAM,0);
if (socket_fd < 0) {
error(1,errno,"socket create failed");
} struct sockaddr_un servaddr = {0};
char *local_path = argv[1];
unlink(local_path);
servaddr.sun_family = AF_LOCAL;
strcpy(servaddr.sun_path,local_path); if (bind(socket_fd,(struct sockaddr*)&servaddr,sizeof(servaddr)) < 0) {
error(1,errno,"bind failed");
} char recv_buff[1024] = {0};
struct sockaddr_un clnt_addr = {0};
socklen_t len = sizeof(clnt_addr); while (true) {
if (recvfrom(socket_fd,recv_buff,1024,0,(struct sockaddr*)&clnt_addr,&len) == 0) {
printf("client quit\n");
break;
}
printf("receive: %s\n",recv_buff); char send_buff[1024] = {0};
sprintf(send_buff,"%s",recv_buff); size_t nbytes = strlen(send_buff);
printf("now sending: %s\n",send_buff); if (sendto(socket_fd,send_buff,nbytes,0,(struct sockaddr*)&clnt_addr,len) != nbytes) {
error(1,errno,"sento error");
}
} close(socket_fd); return 0;
}

使用数据报套接字就无须再使用listen和bind,同时要使用recvfrom和sento来进行数据收发

客户端

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h>
#include <error.h>
#include <errno.h> int main(int argc,char *argv[])
{
if (argc != 2) {
error(1,0,"usage: unixdataclient <local path>");
} int sockfd;
struct sockaddr_un clnt_addr = {0},serv_addr = {0}; sockfd = socket(AF_LOCAL,SOCK_DGRAM,0);
if (sockfd < 0) {
error(1,errno,"create socket failed");
} clnt_addr.sun_family = AF_LOCAL;
strcpy(clnt_addr.sun_path,tmpnam(NULL)); if (bind(sockfd,(struct sockaddr*)&clnt_addr,sizeof(clnt_addr)) < 0) {
error(1,errno,"bind failed");
} serv_addr.sun_family = AF_LOCAL;
strcpy(serv_addr.sun_path,argv[1]); char send_buff[1024] = {0};
char recv_buff[1024] = {0}; while (fgets(send_buff,1024,stdin) != NULL) {
int i = strlen(send_buff);
if (send_buff[i-1] == '\n') {
send_buff[i-1] = 0;
}
size_t nbytes = strlen(send_buff);
printf("now sending %s\n",send_buff); if (sendto(sockfd,send_buff,nbytes,0,(struct sockaddr*)&serv_addr,sizeof(serv_addr)) != nbytes) {
error(1,errno,"sendto error");
} int n = recvfrom(sockfd,recv_buff,1024,0,NULL,NULL);
recv_buff[n] = 0; fputs(recv_buff,stdout);
fputs("\n",stdout);
} return 0;
}

这段代码和UDP套接字编程相似,但有一点较为不同,这里要将本地套接字bind到本地一个路径上,因为要指定一个本地路径,以便在服务端回包时,可以正确找到地址

运行测试

服务端

$ sudo ./local_dgram_server /tmp/unixdata.sock
receive: Hello!
now sending: Hello!

客户端

$ sudo ./local_dgram_client /tmp/unixdata.sock
Hello!
now sending Hello!
Hello!

Linux Socket网络编程: TCP/UDP与本地套接字的更多相关文章

  1. 网络编程(基于udp协议的套接字/socketserver模块/进程简介)

    一.基于UDP协议的套接字 TCP是建立可靠连接,并且通信双方都可以以流的形式发送数据.相对TCP,UDP则是面向无连接的协议. 使用UDP协议时,不需要建立连接,只需要知道对方的IP地址和端口号,就 ...

  2. Socket网络编程(TCP/IP/端口/类)和实例

    Socket网络编程(TCP/IP/端口/类)和实例 原文:C# Socket网络编程精华篇 转自:微冷的雨 我们在讲解Socket编程前,先看几个和Socket编程紧密相关的概念: TCP/IP层次 ...

  3. Socket网络编程-TCP编程

    Socket网络编程-TCP编程 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.socket介绍 1>.TCP/IP协议 2>.跨网络的主机间通讯 在建立通信连接的 ...

  4. 【转】PHP实现系统编程(四)--- 本地套接字(Unix Domain Socket)

    原文:http://blog.csdn.net/zhang197093/article/details/78143687?locationNum=6&fps=1 --------------- ...

  5. Socket网络编程TCP、UDP演示样例

    Socket网络编程: 1) OSI(了解): 国际标准化组织ISO(International Orgnization for Standardization)指定了网络通信的模型:开放系统互联(O ...

  6. Python中的socket网络编程(TCP/IP,UDP)讲解

    在网络编程中的一个基本组件就是套接字(socket).套接字基本上是两个端点的程序之间的"信息通道".程序可能分布在不同的计算机上,通过套接字互相发送信息.套接字包括两个:服务器套 ...

  7. Linux Socket 网络编程

    Linux下的网络编程指的是socket套接字编程,入门比较简单.在学校里学过一些皮毛,平时就是自学玩,没有见识过真正的socket编程大程序,比较遗憾.总感觉每次看的时候都有收获,但是每次看完了之后 ...

  8. 【转】Linux C 网络编程——TCP套接口编程

    地址:http://blog.csdn.net/matrix_laboratory/article/details/13669211 2. socket() <span style=" ...

  9. 32.网络编程TCP/UDP服务

    网络编程TCP: 服务器端口了解: port:0~65535 web服务:80 邮箱服务:556 0~1024:为服务默认的公认端口,一般我们不能用 套接字:socket socket作用 ip:po ...

  10. 【linux高级程序设计】(第十三章)Linux Socket网络编程基础 2

    BSD Socket网络编程API 创建socket对象 int socket (int __domain, int __type, int __protocol) :成功返回socket文件描述符, ...

随机推荐

  1. 20193314白晨阳 实验一《Python程序设计》实验报告

    实验一 20193314 2020-2021-2 <Python程序设计>实验1报告 课程:<Python程序设计> 班级: 201933 姓名: 白晨阳 学号:2019331 ...

  2. 调用mglearn时的报错 TypeError: __init__() got an unexpected keyword argument 'cachedir'

    import mglearn的时候发生的报错 原因是调用了joblib包中的memory类,但是cachedir这个参数已经弃用了 查到下面帖子之后改掉cachedir解决问题 https://blo ...

  3. 关于Maven的使用

    Maven基础入门 一.maven是什么 Apache Maven,是一个项目管理及自动构建的工具,有Apache软件基金会所提供. Maven是用Java语言编写的,是一款可以跨平台的软件. Mav ...

  4. 生产环境实现Docker部署宝塔面板

    生产环境中,为了避免极小概率的数据丢失,我们将容器内的宝塔文件映射到宿主机的目录中(您之后安装的 Nginx.MySQL 等服务均会挂载到宿主机目录).该方法是 Docker 部署宝塔面板的最优方案, ...

  5. redis linux源码安装

    1.官网下载安装包 2.解压 3.确认GCC环境 4.make 5.修改conf配置文件守护进程daemonize yes和默认密码requirepass password 5.启动 安装目录src/ ...

  6. margin:auto实现盒子水平垂直居中

    margin:auto为什么不垂直居中 margin:auto是具有强烈计算意味的关键字,用来计算元素对应方向上应该获得的剩余空间大小. 行内元素margin:auto; 不能水平居中在一行的中央位置 ...

  7. MySql8错误记录.巨坑!File './binlog.index' not found

    mysql8存在大小写敏感,若要设置不敏感,需要在mysql初始化时设置:然后库中已有项目存在,mysql备份文件夹后无法重启,还原数据后存在权限问题,更改文件夹权限后,发现仍然不行,将SELinux ...

  8. DRF提供的请求与响应类

    一 内容协商 drf除了在数据序列化部分简写代码以外,还在视图中提供了简写操作.所以在django原有的django.views.View类基础上,drf封装了多个视图子类出来提供给我们使用. Dja ...

  9. Python 3 os.walk读取指定文件路径后,打印路径参数为空

    今天有时间自己尝试了一下os.walk的小实验,结果出现了一个小问题:在交互模式下,运行我的python脚本,没有打印任何内容 返回去看一下test.py内容 返回去看一下文件路径是否正确: 看着好像 ...

  10. sql处理重复的列,更好理清分组和分区

    一.分组统计.分区排名 1.语法和含义: 如果查询结果看得有疑惑,看第二部分-sql处理重复的列,更好理清分组和分区,有建表插入数据的sql语句 分组统计:GROUP BY 结合 统计/聚合函数一起使 ...