首页
关于
Search
1
Lua使用调试库hook函数调用
429 阅读
2
傻瓜式快速搭建l2tp
385 阅读
3
游戏邮件系统数据设计因素
327 阅读
4
Linux内核数据结构kfifo小结(TODO)
313 阅读
5
傻瓜式安装chatgpt-web工具
296 阅读
项目技术
项目思考
开发环境
数据库
编程语言
生活与阅读
哲学
登录
Search
标签搜索
nodejs
npm
Typecho
累计撰写
55
篇文章
累计收到
34
条评论
首页
栏目
项目技术
项目思考
开发环境
数据库
编程语言
生活与阅读
哲学
页面
关于
搜索到
54
篇与
的结果
套接字编程中bind和reuse实践
一般的开发中, 需要使用bind的情况是在服务端。今天遇到一个特殊的使用场景,客户端因为安全性要求,连接时不能使用内核分配的端口,需要使用指定的端口和服务器连接。自然就用到了bind接口。从bind又引入了reuse属性的使用。这里使用gethostbyname_r来做域名到IP地址的转换(curl也是如此),另外像云风skynet则使用getaddrinfo接口,后者用法更简单一些。当然他们都阻塞的。以下是验证代码://anker<lichengman2006@gmail.com> @2021-06-17T20:18:16 //gcc client.c -o client //client www.google.com 80 9090 1 #include <stdio.h> #include <stdlib.h> #include <netdb.h> #include <string.h> #include <arpa/inet.h> #include <unistd.h> int main(int argc, char *argv[]) { if (argc != 5) { fprintf(stderr,"usage: \n" "client $serverIp $serverPort $clientPort $reuse\n" "example: ./client www.google.com 80 9090 1\n"); exit(1); } int h_errnop; int HOSTENT_SIZE = 9000; struct hostent *buf = NULL; struct hostent *he = NULL; char *hostname = argv[1]; int port = atoi(argv[2]); buf = calloc(1, HOSTENT_SIZE); if(!buf) exit(1); // int gethostbyname_r(const char *name, // struct hostent *ret, char *buf, size_t buflen, // struct hostent **result, int *h_errnop); // name:Here name is either a hostname, or an IPv4 address in standard dot notation (as for inet_addr(3)), or an IPv6 address in colon (and possibly dot) notation // ret:which will be filled in on success, and a temporary work buffer buf of size buflen // result: will point to the result on success. In case of an error or if no entry is found result will be NULL. The functions return 0 on success and a nonzero error number on failure gethostbyname_r(hostname, (struct hostent *)buf, (char *)buf + sizeof(struct hostent), HOSTENT_SIZE - sizeof(struct hostent), &he, /* DIFFERENCE */ &h_errnop); if(!he){ /* failure */ he = NULL; /* set return code to NULL */ free(buf); exit(1); } char *curr; struct sockaddr_in serveraddr; for(int i = 0; (curr = he->h_addr_list[i]) != NULL; i++) { //if(he->h_addrtype == AF_INET6){ if(he->h_addrtype == AF_INET){ // only support ipv4 serveraddr.sin_family = he->h_addrtype; memcpy(&serveraddr.sin_addr, curr, sizeof(struct in_addr)); serveraddr.sin_port = htons((unsigned short)port); char s[64] = {'\0'}; inet_ntop(AF_INET, &(serveraddr.sin_addr), s, 64); printf("get ip:%s\n", s); }else{ // just print //struct sockaddr_in6 _addr; //memcpy(&_addr.sin6_addr, curr, sizeof(struct in6_addr)); struct sockaddr_in6 *paddr = (struct sockaddr_in6*)curr; char s[64] = {'\0'}; inet_ntop(AF_INET6, &(paddr->sin6_addr), s, 64); printf("get ipv6:%s\n", s); } } int sockfd; if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { perror("socket"); exit(1); } int enable_reuse = atoi(argv[4]); if(enable_reuse > 0){ enable_reuse = 1; if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &enable_reuse, sizeof(int)) < 0){ perror("set reuse failed\n"); exit(1); }else{ printf("set reuse succ!\n"); } } int bindport = atoi(argv[3]); struct sockaddr_in clientaddr; clientaddr.sin_family = AF_INET; clientaddr.sin_addr.s_addr = htonl(INADDR_ANY); clientaddr.sin_port = htons(bindport); if ((bind(sockfd, (struct sockaddr*)&clientaddr, sizeof(clientaddr))) != 0) { printf("socket bind failed...\n"); exit(0); } else printf("Socket successfully binded.. port:%d\n", bindport); if (connect(sockfd, (struct sockaddr *)&serveraddr, sizeof(struct sockaddr)) == -1) { perror("connect"); exit(1); }else{ printf("connect succ!\n"); sleep(100); close(sockfd); } }通过代码可以验证客户端可以通过bind来使用指定端口连接服务器,以下均验证bind时,reuse属性导致情况:如不使用reuse模式,在第一个程序结束时,本地端口还处于TIME_WAIT状态。此时第2个客户端无论是否reuse都是无法再次使用的,提示绑定失败。在reuse模式下导致的TIME_WAIT状态,第2个客户端可以通过reuse模式使用,但是非reuse模式会绑定失败。在reuse模式下,可以同时启动多个客户端。但前提是连接不同的服务器地址。因为如果连接两个服务器地址相同>,此时网络五元组相同,协议栈根本无法区分,会报connect: Cannot assign requested address错误。理论上讲(因为没有亲自证实),reuse模式对listen是无效的。
2021年06月26日
87 阅读
0 评论
0 点赞
关于线程栈大小设置实验
每个线程的创建都是有消耗的。这里关注线程栈大小和设置。在Redis的IO线程中也是有设置线程栈为4MB大小的。另外验证的ulimit -s设置时,只能设置为比当前更小,不能扩大。//anker<lichengman2006@gmail.com> @2021-06-20T18:12:11 //gcc thread_stack_size.c -o thread_stack_size -D_GNU_SOURCE -lpthread #include <errno.h> #include <stdio.h> #include <stdlib.h> #include <pthread.h> size_t get_current_thread_stacksize(){ pthread_attr_t attr; size_t stacksize; //pthread_attr_init(&attr); pthread_getattr_np(pthread_self(), &attr); int err = pthread_attr_getstacksize(&attr,&stacksize); if(err != 0){ perror("cannot get thread stack size\n"); exit(err); } pthread_attr_destroy(&attr); return stacksize; } void *thread_entry(void *arg) { char * msg = (char *)arg; size_t stacksize = get_current_thread_stacksize(); printf("get %s stack size(bytes):%ld\n", msg, stacksize); } int main(int argc, char *argv[]){ if(argc != 2){ printf("usage:" "thread_stack_size $sizInMB\n" ); exit(1); } size_t main_stacksize = get_current_thread_stacksize(); printf("get main thread stack size(bytes):%ld\n", main_stacksize); pthread_t threadId; if (pthread_create(&threadId, NULL, thread_entry, "first thread") != 0) { perror("cannot create sub thread"); exit(errno); } pthread_join(threadId, NULL); pthread_attr_t attr; pthread_attr_init(&attr); if ( 0 != pthread_attr_setstacksize(&attr, 1024*1024*atoi(argv[1]))){ //if ( 0 != pthread_attr_setstacksize(&attr, 1024*1024*10)){ perror("set stack size failed."); exit(errno); } pthread_t threadId2; if (pthread_create(&threadId2, &attr, thread_entry, "second thread") != 0) { printf("cannot create sub thread"); exit(errno); } pthread_attr_destroy(&attr); pthread_join(threadId2, NULL); return 0; }pthread_attr_setstacksize函数说明提到:The stack size attribute determines the minimum size (in bytes) that will be allocated for threads created using the thread attributes object attr.他设置的是栈大小的最小值,另外系统也是有限制栈的16KB最小值PTHREAD_STACK_MIN (16384) bytes.> ulimit -s 4096 > ./thread_stack_size 5 get main thread stack size(bytes):4194304 get first thread stack size(bytes):4194304 get second thread stack size(bytes):5242880 > ./thread_stack_size 3 get main thread stack size(bytes):4190208 get first thread stack size(bytes):4194304 get second thread stack size(bytes):4194304留意以下事实:子线程和主线程的栈大小是不一致的。pthread_attr_setstacksize是设置比ulimit还小的值,系统会自动采用最大的最小值。更详细的策略可以参考glibc的allocatestack.c中的allocate_stack函数,其中有对齐等考虑因素。
2021年06月26日
30 阅读
0 评论
0 点赞
僵尸进程实验
僵尸进程产生的原因是当前还没有其他进程来回收他的尸体。每个进程死亡后系统都有部分资源被持有没有被释放。保留的资源有PID和退出码以及CPU时间、内存使用量等子进程一生的信息。如果他的父进程也已经死亡,则会由系统的INIT进程接管收尸。因为进程已经死亡,使用Kill是无法杀死他的。可能通过杀死其父进程来结束。每个进程在死亡时都会通过SIGCHILD信号通知父进程。#include <stdio.h> #include <stdbool.h> #include <stdlib.h> #include <sys/types.h> #include <unistd.h> #include <signal.h> #include <sys/wait.h> void signal_handler(int signo){ printf("this is in the sig handler:%d\n", signo); } bool register_signal(int signo){ struct sigaction act; struct sigaction oldact; act.sa_handler = signal_handler; sigemptyset(&act.sa_mask); act.sa_flags = 0; if(sigaction(signo, &act, &oldact) < 0) return false; return true; } int main(){ pid_t pid; pid = fork(); if(pid<0) { /* 如果出错 */ printf("error occurred!\n"); } else if(pid==0) { /* 子进程 */ exit(0); } else { /*进程对SIGCHLD默认动作是忽略,对SIGUSR1,SIGUSR2是终止 * 这里选用kill -SIGUSR1 $parentPID 演示。 * 如果不注册会因为终止进程而无法观察到sleep被中断 * */ register_signal(SIGUSR1); /* 父进程 */ perror("going to sleep\n"); sleep(300); /* 休眠300秒 */ perror("sleep end\n"); wait(NULL); /* 获取僵尸进程的退出信息 */ } return 0; }运行结果:[anker@ms lab]$ ./a.out going to sleep : Success this is in the sig handler:10 sleep end : Interrupted system call
2021年06月26日
13 阅读
0 评论
0 点赞
关于近期博客事件反思
本周服务器故障,导致笔记丢失。回顾事件,有多个原因导致损失。服务器应该保持有开发账号和维护账号,他们进行不同的职责。在开发账号出现问题不能登录时,可以由维护账号来挽救。事故后不应该草率决定重建。应该先确认备份的有效。在重建后再决定是否清理。类似云机器可以先回收机器但保留磁盘,在使用新服务器后挂载旧磁盘。切换环境要慎重。从Centos8切换到Ubuntu20导致环境不熟悉,导致重建各种低效。没有持续有效的备份机制。一直是有想起就备份,但是没想到最近的一次备份还是两年前。针对以上问题:服务器创建两个权限账号。对笔记进行每天定期备份。使用bypy工具,每天凌晨自动打包自动上传百度云盘备份。
2021年06月26日
30 阅读
0 评论
0 点赞
1
...
10
11