首页
关于
Search
1
Lua使用调试库hook函数调用
333 阅读
2
傻瓜式快速搭建l2tp
328 阅读
3
Linux内核数据结构kfifo小结(TODO)
269 阅读
4
傻瓜式安装chatgpt-web工具
256 阅读
5
游戏邮件系统数据设计因素
242 阅读
项目技术
项目思考
开发环境
数据库
编程语言
生活与阅读
哲学
登录
Search
标签搜索
nodejs
npm
韭菜笔记
累计撰写
55
篇文章
累计收到
0
条评论
首页
栏目
项目技术
项目思考
开发环境
数据库
编程语言
生活与阅读
哲学
页面
关于
搜索到
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日
79 阅读
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日
29 阅读
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