(前期使用Ubuntu18.04,后期换成了Deepin20,但是二者都是Debian系的所以各种操作不耽误)

  • 什么是Socket

英语socket是插座,插孔的意思,中文译作套接字;

插销和插座插在一起,就能通电,引申得到两个socket套接在一起就可以通信;

既然是插座和插销,所以socket一定是成对出现的;

IP地址:在网络环境中唯一标识一台主机;

Port:在主机中唯一标识一个进程;

IP地址+Port:在网络环境中唯一标识一个进程,这个进程就是Socket;

Socket是Linux的一种文件类型(伪文件,不会占用实际的存储空间;Linux七种文件类型:占存储空间:普通文件、目录、软连接,不占存储空间:字符设备、块设备、管道、套接字);

Socket是全双工的,一个文件描述符对应两个缓冲区,分别进行读和写:

  • 网络字节序转化

数据在网络中传输就要转化成二进制形式;

主机字节序是小端法,高位存在高地址,低位存在低地址;网络字节序是大端法,高位存在低地址,低位存在高地址:(int类型四个字节,每个字节八位,而一个十六进制数四位,所以一个字节存两个十六进制数)

网络字节序和主机字节序转化的函数:

uint32_t    htonl(uint32_t    hostlong)//uint32_t:32位无符号整型;htonl:host to network long,32位数值从主机字节序转为网络字节序;主机发送数据到网络环境时用;

uint16_t    htons(uint16_t    hostshort)//htons:host to network short,16位数值从主机字节序转为网络字节序;

uint32_t    ntohl(uint32_t    netlong)//32位数值从网络字节序转为主机字节序;主机从网络环境接收数据时用;

uint16_t    ntohl(uint16_t    netshort)

  • 专门用于IP地址转化的函数

#include <arpa/inet.h>

int  inet_pton(int  af,const  char  *src,void  *dst)//将IP地址由点分十进制转为网络字节序;af是地址族,有两个值AF_INET表示IPv4,AF_INET6表示IPv6;src是待转换的点分十进制IP字符串,const表示只读;dst是转换后的IP;

int  inet_ntop(int  af,const  void  *src,char  *dst,socklen_t  size)//将IP地址由网络字节序转为点分十进制字符串;af地址族;src待转换的网络字节序IP;dst转换后的点分十进制字符串(首地址);size是点分十进制字符串长度;

htonl等函数操作的是数值类型,inet_pton操作的是字符串;

  • sockaddr数据结构

最初的struct  sockaddr是一个描述IPv4的结构体,包括地址类型(16bit)和地址数据(32Byte);

后来经过改进,改为struct  sockaddr_in,包括地址类型=AF_INET(16bit)、端口号(16bit)、IP地址(32bit)和填充(8bit),大小没变,只是成员细分了;

struct  sockaddr_in成员:

成员1是协议族,成员2是端口号,都是typedef的数据类型;

成员3是IP地址,又是一个结构体,只有一个成员:

现在只有struct  sockaddr_in,struct  sockaddr已经废弃了,但是现在的函数传参还是struct  sockaddr类型,比如bind()函数有一参struct  sockaddr  *addr,现在只能先定义struct  sockaddr_in  addr,再传参(struct  sockaddr  *)&addr,因此在程序中看到这种强制类型转换要知道是什么意思,这是历史遗留问题,只能通过强转解决;

  • socket()函数

参数int  type:SOCK_STREAM表示“流式”传输,SOCK_DGRAM表示“报式”传输;

参数int  protocol:传0表示使用该type的默认协议,流式默认协议是TCP,报式默认协议是UDP;

一般参数int  domain可取两个值AF_INET和AF_INET6,参数int  type可取两个值SOCK_STREAM和SOCK_DGRAM,参数int  protocol取0;

返回值:成功,返回新创建的socket文件的文件描述符;失败,返回-1;

PS:什么是文件描述符:文件IO操作中,调用open()函数会得到一个结构体,其中包含了操作这个文件所需要的所有文件属性,指向这个结构体的指针被保存在当前进程空间中的一个数组里,结构体指针所在的数组下标就是文件描述符(fd);因此文件描述符是个int正整数;fopen()函数依赖于open()函数,而调用fopen函数会得到一个File *型指针,指向的也是一个结构体,fd是这个结构体的一个成员;

  • bind()函数

  • listen()函数

指定socket可以同时建立多少个连接,如果参数int  backlog为100,并且此时已经建立了100个连接,那么第101个来了就得等着直到前面100个有断开连接的;

操作系统限定最大是128;

  • accept()函数

参数2是个传出参数,只需要定义一个(struct  sockaddr)struct  sockaddr_in  addr,不需要初始化直接取地址传进去,经过函数调用后addr已经完成初始化了,是一个新的socket的sockaddr结构体;返回值也是一个新socket的fd;

作用:服务端调用accept()函数后阻塞,直到收到来自客户端的连接,这时创建一个新的socket负责与客户端通信,而服务端继续阻塞等待下一个客户端的连接;

也就是说accept()函数是只在服务端调用的;

  • connect()函数

用于连接服务器,参数看起来和bind()函数一样,但是这里参数2是初始化好的对方socket的sockaddr结构体、3是结构体大小;

只在客户端调用;

  • 服务端编程
vim  server.c
#include <stdio.h>
#include <stdlib.h>//包含exit()
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>//包含struct sockaddr_in
#include <ctype.h>//包含toupper()
#include <strings.h>//包含bzero()#define SERV_IP INADDR_ANY  //INADDR_ANY是192.168.x.x内部地址
#define SERV_PORT 8888int main(void)
{int serv_fd, clie_fd;struct sockaddr_in serv_addr, clie_addr;socklen_t clie_addr_len;char buf[BUFSIZ], clie_ip_buf[BUFSIZ];//BUFSIZ是内置的宏,专门用来指定buf大小int n, i;serv_fd = socket(AF_INET, SOCK_STREAM, 0);bzero(&serv_addr, sizeof(serv_addr));//类似于memeset(),bzero()是直接初始化为0serv_addr.sin_family = AF_INET;serv_addr.sin_port = htons(SERV_PORT);//host to network short转成网络字节序serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);//host to network long转成网络字节序;bind(serv_fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));listen(serv_fd, 128);clie_addr_len = sizeof(clie_addr);clie_fd = accept(serv_fd, (struct sockaddr *)&clie_addr, &clie_addr_len);//参数3是传入传出参数,因为传入,所以初始化,因为传出,所以取地址printf("client IP: %s,  client port: %d\n", inet_ntop(AF_INET, &clie_addr.sin_addr.s_addr, clie_ip_buf, sizeof(clie_ip_buf)), ntohs(clie_addr.sin_port));while(1){ n = read(clie_fd, buf, sizeof(buf));//从客户socket里读,读到buf里,读的空间是sizeof(buf)这么大,读到了n个字符for(i = 0; i < n; i++)buf[i] = toupper(buf[i]);write(clie_fd, buf, n);//往clie_fd里写,写的是buf里的东西,写n个字符;}close(serv_fd);close(clie_fd);return 0;
}
:wq
gcc server.c -o server
./server
  • 客户端编程
vim client.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>//包含struct sockaddr_in
#include <string.h>//包含memset()#define SERV_IP "127.0.0.1"
#define SERV_PORT 8888int main(void)
{int clie_fd;struct sockaddr_in serv_addr, clie_addr;char buf[BUFSIZ];//BUFSIZ是内置的宏,专门用来指定buf大小int n;clie_fd = socket(AF_INET, SOCK_STREAM, 0);memset(&serv_addr, 0, sizeof(serv_addr));//把这一块内存空间全部清成0;memset()用来为新申请的内存做初始化serv_addr.sin_family = AF_INET;serv_addr.sin_port = htons(SERV_PORT);//host to network short转成网络字节序inet_pton(clie_fd, SERV_IP, &serv_addr.sin_addr.s_addr);//IP字符串转成网络字节序;connect(clie_fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));while(1){fgets(buf, sizeof(buf), stdin);//参数1:目的缓冲区指针,参数2:缓冲区大小,参数3:源数据流;stdin:标准输入write(clie_fd, buf, strlen(buf));//键盘输入hello/n,fgets后buf里的是hello/n/0,strlen(buf)取的是/0之前的长度n = read(clie_fd, buf, sizeof(buf));//写完之后发给服务端了,服务端会响应回来,现在该读了write(STDOUT_FILENO, buf, n);//把读到的服务器的响应写到屏幕}close(clie_fd);return 0;
}
:wq
gcc client.c -o client
./client

客户端发送小写字符串,服务端返回大写字符串;

  • 阻塞

程序运行后并不会疯狂地无限循环,因为accept()、read()、write()等函数都是阻塞的,只有客户端连接、发送数据才会推动循环的进行;

  • 查看C函数帮助文档:以socket()函数为例:
:!man socket  # 在Vim中
man socket  # 在终端中

  • Socket工作原理

serv_fd通过accept()返回的是一个和客户端相同sockaddr的socket,对它读写就相当于对客户端读写;

两个clie_fd通过相同的IP和port能够通信;

服务端clie_fd调用write(),将内容写到写缓冲区,在网络的作用下,也写到了客户端的读缓冲区,这时客户端的read()函数就解除阻塞并读到内容;客户端clie_fd调用write()也是同理;

一个socket有两个缓冲区,每个缓冲区都是根据管道的特性打造的,即一端只能读,一端只能写;两个管道实现了socket的全双工通信;

上面提到的缓冲区和char  buf [ ]缓冲区是不同的,前者位于内核区,后者位于用户区stack堆,一点关系都没有;

void exit(int status)来自stdlib.h作用是结束当前进程,退出程序;参数是0表示正常退出,不是0都表示异常退出;

  • 如果Ctrl+C先关闭server再关闭client的话,下次再启动client发消息会闪退:

因为在客户端连接的情况下Ctrl+C关闭服务端,会使服务端进程处于TIME_WAIT状态,不会真正的关闭,依然占用着端口号,所以下次启动会出错;如果我们写了错误处理代码,就会报错;

查看网络进程状态:以8888端口号为例:

netstat -apn | grep 8888
  • 为了方便知道出错的原因,要补充错误处理代码:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>//包含struct sockaddr_in
#include <ctype.h>//包含toupper()
#include <strings.h>//包含bzero()#define SERV_IP INADDR_ANY  //INADDR_ANY是192.168.x.x内部地址
#define SERV_PORT 8888int main(void)
{int serv_fd, clie_fd;struct sockaddr_in serv_addr, clie_addr;socklen_t clie_addr_len;char buf[BUFSIZ], clie_ip_buf[BUFSIZ];//BUFSIZ是内置的宏,专门用来指定buf大小int n, i;serv_fd = socket(AF_INET, SOCK_STREAM, 0);if(serv_fd == -1){perror("socket error");exit(1);}bzero(&serv_addr, sizeof(serv_addr));//类似于memeset(),bzero()是直接初始化为0serv_addr.sin_family = AF_INET;serv_addr.sin_port = htons(SERV_PORT);//host to network short转成网络字节序serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);//host to network long转成网络字节序;if(bind(serv_fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == -1){perror("bind error");exit(1);}if(listen(serv_fd, 128) == -1){perror("listen error");exit(1);}clie_addr_len = sizeof(clie_addr);clie_fd = accept(serv_fd, (struct sockaddr *)&clie_addr, &clie_addr_len);//参数3是传入传出参数,因为传入,所以初始化,因为传出,所以取地址if(clie_fd == -1){perror("accept error");exit(1);}printf("client IP: %s,  client port: %d\n", inet_ntop(AF_INET, &clie_addr.sin_addr.s_addr, clie_ip_buf, sizeof(clie_ip_buf)), ntohs(clie_addr.sin_port));while(1){   n = read(clie_fd, buf, sizeof(buf));//从客户socket里读,读到buf里,读的空间是sizeof(buf)这么大,读到了n个字符for(i = 0; i < n; i++)buf[i] = toupper(buf[i]);write(clie_fd, buf, n);//往clie_fd里写,写的是buf里的东西,写n个字符;}close(serv_fd);close(clie_fd);return 0;
}
  • 添加错误处理使代码冗杂,改用自定义函数、封装容错模块、多文件联合编译:

wrap.c(包含了所有自定义容错函数):

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <errno.h>void perr_exit(const char *s)
{perror(s);exit(-1);
}int Socket(int domain, int type, int protocol)
{int fd;if((fd = socket(domain, type, protocol)) < 0)perr_exit("socket error");return fd;
}int Accept(int fd, struct sockaddr *addr, socklen_t *len){int new_fd;
again:if((new_fd = accept(fd, addr, len)) < 0){if((errno == ECONNABORTED) || (errno == EINTR))goto again;elseperr_exit("accept error");}return new_fd;
}int Bind(int fd, const struct sockaddr *addr, socklen_t len)
{int result;if((result = bind(fd, addr, len)) < 0)perr_exit("bind error");return result;
}int Connect(int fd, const struct sockaddr *addr, socklen_t len)
{int result;if((result = connect(fd, addr, len)) < 0)perr_exit("connect error");return result;
}int Listen(int fd, int backlog)
{int result;if((result = listen(fd, backlog)) < 0)perr_exit("listen error");return result;
}/*
size_t nbytes   读的字节数(范围);
ssize_t n       实际读到的字节数;
*/
ssize_t Read(int fd, void *ptr, size_t nbytes)
{ssize_t n;
again:if((n = read(fd, ptr, nbytes)) == -1){if(errno == EINTR)goto again;elseperr_exit("read error");}return n;
}ssize_t Write(int fd, const void *ptr, size_t nbytes)
{ssize_t n;
again:if((n = write(fd, ptr, nbytes)) == -1){if(errno == EINTR)goto again;elsereturn -1;}return n;
}int Close(int fd)
{int n;if((n = close(fd)) == -1)perr_exit("close error");return n;
}//参数3:应该读取的字节数
ssize_t Readn(int fd, void *vptr, size_t n)
{size_t nleft;   //unsigned int 剩余未读字节数ssize_t nread;  //(signed) int 读一次实际读到的字节数char *ptr;ptr = vptr;nleft = n;while(nleft > 0){if((nread = read(fd, ptr, nleft)) < 0){if(errno == EINTR)nread = 0;elsereturn -1;                            }else if(nread == 0)break;nleft -= nread;ptr += nread;}return n - nleft;
}ssize_t Writen(int fd, const void *vptr, size_t n)
{size_t nleft;ssize_t nwritten;const char *ptr;ptr = vptr;nleft = n;while(nleft > 0){if((nwritten = write(fd, ptr, nleft)) <= 0){if(nwritten < 0 && errno == EINTR)nwritten = 0;else return -1;}  nleft -= nwritten;ptr += nwritten;  }    return n;
}static ssize_t my_read(int fd, char *ptr)
{static int read_cnt;static char *read_ptr;static char read_buf[100];if(read_cnt <= 0){
again:if((read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0){if(errno = EINTR)    goto again;return -1;}else if(read_cnt == 0);return 0;read_ptr = read_buf;}read_cnt--;*ptr = *read_ptr++;return 1;
}/*
readline --- fgets
传出参数vptr
*/
ssize_t Readline(int fd, void *vptr, size_t maxlen)
{ssize_t n, rc;char c, *ptr;ptr = vptr;for(n = 1; n < maxlen; n ++){if((rc = my_read(fd, &c)) == 1){*ptr++ = c;if(c == '\n')break;}else if(rc == 0){*ptr = 0;return n - 1;}elsereturn -1;}*ptr = 0;return n;
}int Fork()
{int pid;if((pid = fork()) < 0)perr_exit("fork error");return pid;
}

wrap.h:

#ifndef __WRAP_H_
#define __WRAP_H_void perr_exit(const char *s);
int Socket(int domain, int type, int protocol);
int Accept(int fd, struct sockaddr *addr, socklen_t *len);
int Bind(int fd, const struct sockaddr *addr, socklen_t len);
int Connect(int fd, const struct sockaddr *addr, socklen_t len);
int Listen(int fd, int backlog);
ssize_t Read(int fd, void *ptr, size_t nbytes);
ssize_t Write(int fd, const void *ptr, size_t nbytes);
int Close(int fd);
ssize_t Readn(int fd, void *vptr, size_t n);
ssize_t Writen(int fd, const void *vptr, size_t n);
static ssize_t my_read(int fd, char *ptr);
ssize_t Readline(int fd, void *ptr, size_t maxlen);
int Fork();#endif

server.c:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <strings.h>#include "wrap.h"//包含自定义的函数#define SERV_IP INADDR_ANY
#define SERV_PORT 8888int main(void)
{int serv_fd, clie_fd;struct sockaddr_in serv_addr, clie_addr;socklen_t clie_addr_len;char buf[BUFSIZ], clie_ip_buf[BUFSIZ];int n, i;serv_fd = Socket(AF_INET, SOCK_STREAM, 0);//用自定义的Socket()bzero(&serv_addr, sizeof(serv_addr));serv_addr.sin_family = AF_INET;serv_addr.sin_port = htons(SERV_PORT);serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);Bind(serv_fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));Listen(serv_fd, 128);clie_addr_len = sizeof(clie_addr);clie_fd = Accept(serv_fd, (struct sockaddr *)&clie_addr, &clie_addr_len);//用自定义的Accept()printf("client IP: %s,  client port: %d\n", inet_ntop(AF_INET, &clie_addr.sin_addr.s_addr, clie_ip_buf, sizeof(clie_ip_buf)), ntohs(clie_addr.sin_port));while(1){ n = Read(clie_fd, buf, sizeof(buf));for(i = 0; i < n; i++)buf[i] = toupper(buf[i]);Write(clie_fd, buf, n);}Close(serv_fd);Close(clie_fd);return 0;
}

client.c同样换用自定义的函数;

联合编译并执行:

为什么不直接:

因为联合编译时,如果修改其中一个.c文件,另一个未修改的不需要gcc -c,分开编译可以只编译修改的,在工程特别大的时候可以节省时间;

  • 网络编程涉及的errno和错误处理

accept():

EINTER(Error INTERrupted):这个系统调用(system call)让一个被捕捉(caught)的信号(signal)打断了(interrupted);

ECONNABORTED(Error CONNection ABORTED):连接(connection)异常断开了(aborted);

以上两个ERROR都是在程序阻塞的时候被打断了,处理方式是要么重启即goto again,要么退出;connect()函数不会遇到这种ERROR,因为connect()是客户端主动发起的,不阻塞;

read():

返回值:

1、> 0:可能==sizeof(buf)也可能<sizeof(buf);

2、==0:数据读完,读到文件/管道/Socket的末尾,对面Socket写端关闭;

3、== -1:异常

1)errno == EINTR:被信号中断,此时重启或退出;

2)errno == EAGAIN或EWOULDBLOCK:非阻塞方式读,并且没有数据;

3)其他:出现错误,perr_exit();

  • read()、write()函数的参数size_t、返回值ssize_t

size_t是自定义的描述size的无符号整型,应该读/写的范围;ssize_t是有符号整型,实际读/写的范围;

  • 自己封装的Readn()函数什么时候用

从Socket里读数据,Socket的数据是从网络中接收到的,而以太网帧最大1500字节,如果我的需求是读4096字节,那么我用read()函数最多读1500字节就返回了,而我们要求读4096字节然后返回,因此自己封装一个Readn(),即通过重复调用read()实现读n个字节;

  • 三次握手和四次握手

1、网络层是不稳定的,容易受硬件影响(现在随着技术进步,影响已经很小了),比如有路由器宕机就会影响网络层质量,也就是说网络层有丢包的风险;

2、由于网络层的不稳定性,传输层也分两类:

1)完全不弥补:顺应网络层的特点,以UDP为代表,面向无连接的报文传输,电报发报文的特点就是接收无应答,丢包不重传;

2)完全弥补:弥补网络层的缺陷,以TCP为代表,面向连接的数据包传输, 接收有应答,丢包会重传;

3、TCP既然是面向连接的,就要先建立连接再发数据,如果由于网络层的不稳定性导致连接断开,就得重新建立连接,发送完数据还要关闭连接;

4、上述建立连接的过程就是三次握手:

1)Client----SYN 1000(0)---->Server

客户端向服务端发起请求:包号是1000,字节数是0(即携带数据的长度是0字节);

2)Client<----SYN 8000(0)ACK 1001----Server

服务端响应客户端并向客户端发起请求:响应:确认收到1001之前的,下次发1001;请求:包号是8000,字节数是0;

3)Client----ACK 8001---->Server

客户端响应服务器:8001之前的包收到,从8001开始发吧;

5、三次握手完成,接下来就是发数据(不一定非得一来一回):

1)Client----1001(20)ACK 8001---->Server

客户端确响应务端并向服务端发数据:响应:确认收到8001之前的,下次发8001;发数据:包号是1001,长度是20(即1001~1020);

2)Client<----8001(40)ACK 1021----Server

服务端响应客户端并向客户端发数据:响应:确认收到1021之前的,下次发1021;发数据:包号是8001,长度是40(即8001~8040);

3)Client----ACK 8041----Server

客户端响应服务端:确认收到8041之前的,下次发8041;

6、发完之后关闭连接的过程就是四次握手:

为什么有4次:因为允许半关闭状态;什么是半关闭状态:一端关闭另一端不关闭,客户端请求关闭并收到服务端的确认后,客户端能接收并确认服务端的数据,但不再给服务端发数据;

1)Client----FIN 1021(0)ACK 8041---->Server

客户端请求关闭:包号是1021,字节数是0;并且确认8041;

2)Client<----ACK 1022----Server

服务端确认收到1021;此时客户端关闭,不再发数据,但能接收数据并发确认包;

3)Client<----FIN 8041(0)ACK 1022----Server

服务端请求关闭,包号是8041,字节数是0;并且确认1022;

4)Client----ACK 8042---->Server

客户端确认收到8042;此时服务端关闭,不再发数据;

注意从三次握手、发送数据到四次握手,请求序列号和确认序列号都是连贯的;

  • 多进程Server
#include <stdio.h>
#include <stdlib.h> //exit()
#include <unistd.h> //close()
#include <arpa/inet.h> //网络编程有关函数
#include <strings.h> //bzero()
#include <ctype.h> //toupper()
#include <sys/wait.h> //signol()、WNOHANG
#include <signal.h> //SIGCHLD
#include "wrap.h" //自定义函数们#define SERV_IP "127.0.0.1"
#define SERV_PORT 8888//用来处僵尸进程的
void wait_child(int signo)
{while(waitpid(0, NULL, WNOHANG) > 0)return;
}int main(void)
{int lfd, cfd;struct sockaddr_in serv_addr, clie_addr;socklen_t clie_addr_len;pid_t pid;char buf[BUFSIZ], clie_ip[BUFSIZ];int n, i;lfd = Socket(AF_INET, SOCK_STREAM, 0);bzero(&serv_addr, sizeof(serv_addr));serv_addr.sin_family = AF_INET;inet_pton(AF_INET, SERV_IP, &serv_addr.sin_addr.s_addr);serv_addr.sin_port = htons(SERV_PORT);Bind(lfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));Listen(lfd, 128);//无限循环,每连接一个客户端,就创建一个cfd与之连接,并创建一个子进程,产生分支://父进程的cfd没用了,close掉继续等待下一个;//子进程的lfd没用了,退出循环去和客户端通信;while (1){clie_addr_len = sizeof(clie_addr);cfd = Accept(lfd, (struct sockaddr *)&clie_addr, &clie_addr_len);printf("%s:%d\n", inet_ntop(AF_INET, &clie_addr.sin_addr, clie_ip, sizeof(clie_ip)), ntohs(clie_addr.sin_port));pid = Fork();if(pid == 0){close(lfd);break;}else{close(cfd);//用来处理僵尸进程//僵尸进程是子进程死了没有被父进程回收,白白占据了资源signal(SIGCHLD, wait_child);}}if(pid == 0){while(1){n = Read(cfd, buf, sizeof(buf));if(n == 0)//client closed{close(cfd);return 0;}else{for(i = 0; i < n; i ++)buf[i] = toupper(buf[i]);Write(cfd, buf, n);}}}return 0;
}

不用signal()的话会有僵尸进程(STAT为Z+的进程):

  • 多线程Server
#include <stdio.h>
#include <unistd.h> //STDOUT_FILENO
#include <string.h>
#include <arpa/inet.h> //网络编程有关函数
#include <ctype.h> //toupper()
#include <pthread.h>
#include "wrap.h" //自定义函数们#define SERV_IP "127.0.0.1"
#define SERV_PORT 8888
#define MAXLINE 8192//自定义结构体,将socket的addr和fd合并
struct sock_info
{struct sockaddr_in addr;int fd;
};void *process(void *arg)
{int n, i;struct sock_info *ts = (struct sock_info *)arg;char buf[MAXLINE];char str[INET_ADDRSTRLEN];while(1){n = Read(ts->fd, buf, MAXLINE);if(n == 0){printf("client is closed...\n");break;}printf("%s:%d\n", inet_ntop(AF_INET, &ts->addr.sin_addr, str, sizeof(str)), ntohs(ts->addr.sin_port));Write(STDOUT_FILENO, buf, n);for(i = 0; i < n; i ++)buf[i] = toupper(buf[i]);Write(ts->fd, buf, n);}close(ts->fd);return (void *)0;
}int main(void)
{int listen_fd, connect_fd;struct sockaddr_in serv_addr, clie_addr;socklen_t clie_addr_len;struct sock_info ts[256];pthread_t tid;int i = 0;listen_fd = Socket(AF_INET, SOCK_STREAM, 0);bzero(&serv_addr, sizeof(serv_addr));serv_addr.sin_family = AF_INET;inet_pton(AF_INET, SERV_IP, &serv_addr.sin_addr.s_addr);serv_addr.sin_port = htons(SERV_PORT);Bind(listen_fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));Listen(listen_fd, 128);printf("Accepting...\n");//无限循环,主线程负责监听,收到连接后创建子线程与之通信while (1){clie_addr_len = sizeof(clie_addr);connect_fd = Accept(listen_fd, (struct sockaddr *)&clie_addr, &clie_addr_len);ts[i].addr = clie_addr;ts[i].fd = connect_fd;//创建子线程pthread_create(&tid, NULL, process, (void *)&ts[i]);//子线程设置为分离状态,防止僵尸线程pthread_detach(tid);i ++;}return 0;
}

引用pthread.h时不能直接链接:

因为pthread不是Linux默认的库,加个参数就可以了:

  • TO BE CONTINUED. . .

Linux下C语言Socket编程相关推荐

  1. linux下的c socket编程(4)--server端的继续研究

    linux下的C socket编程(4) 延长server的生命周期: 在前面的一个个例子中,server在处理完一个链接之后便会立即结束掉自己,然而这种server并不科学,server因该使能够一 ...

  2. linux下C语言socket网络编程简例

    转自博文:http://blog.csdn.net/kikilizhm/article/details/7858405 在练习写网络编程时,该例给了我帮助,在写服务器时,我把while逻辑位置想法错了 ...

  3. windows环境下C语言socket编程

    最近由于实验需要,要求写一个c程序与java程序通信的软件,为了测试首先写了一个windows环境下c语言的socket(tcp)通信程序. 首先socket通信的步骤:    图一     sock ...

  4. Linux下的C++ socket编程实例

    阅读目录 基本的局域网聊天 客户端服务端双向异步聊天源码 局域网内服务端和有限个客户端聊天源码 完美异步聊天服务端和客户端源码 C++定时器 select异步代码 pthead多线程 服务端: 服务器 ...

  5. Linux下的简单socket编程示例

    API中用到的结构体 #1. struct sockaddr struct sockaddr { u_char sa_len; u_short sa_family; // address family ...

  6. linux c语言工具,Linux下C语言编程环境的工具.doc

    Linux下C语言编程环境的工具 Linux下C语言编程环境的工具 Linux下C语言编程环境的工具 要想在Linux下进行C语言编程,首先得搭建好一个编程环境.这里分别说明一下几个非常有用的软件包. ...

  7. Linux下C语言编程-进程的创建

    Linux下C语言编程-进程的创建 作者:hoyt 1.进程的概念 Linux操作系统是面向多用户的.在同一时间可以有许多用户向操作系统发出各种命令.那么操作系统是怎么实现多用户的环境呢?在现代的操作 ...

  8. 5.3linux下C语言socket网络编程简例

    原创文章,转载请注明转载字样和出处,谢谢! 这里给出在Linux下的简单socket网络编程的实例,使用tcp协议进行通信,服务端进行监听,在收到客户端的连接后,发送数据给客户端:客户端在接受到数据后 ...

  9. linux+下c语言编程项目,精通UNIX下C语言编程与项目实践

    cc -I  //include 目录 -L //静态库目录?动态也可以 -l //小写L,接静态库名称?动态也可以 -DXXX='"XXFF"' //-D直接定义宏 -c 只编译 ...

最新文章

  1. PE文件和COFF文件格式分析——导出表的应用——一种摘掉Inline钩子(Unhook)的方法
  2. monostate 状态_为什么Borg模式比Python中的Singleton模式更好
  3. php采集列表xml代码,php读取xml列表程序
  4. 如何跟下属进行沟通?
  5. stm32f105使用12M外部晶振
  6. Go语言基础:method
  7. 三重积分平均值_2015考研数学考前必须死磕的知识点
  8. Parsing error: The keyword 'const' is reservedeslint
  9. osx php7 imagick,[PHP] MacOS 自带php环境安装imagick扩展踩坑记录 | 码农部落
  10. 《那些年啊,那些事——一个程序员的奋斗史》——35
  11. jvmti_JVMTI标记如何影响GC暂停
  12. idea 调试技巧1
  13. 收藏 | 12 种 NumpyPandas 高效技巧
  14. C# Programming Study #2
  15. android 任意剪切view,Android 任意View切圆角
  16. numpy功能快速查找
  17. Unity 3D实现帧同步技术
  18. Apache 架构师的 30 条设计原则
  19. Java实现计数排序
  20. 您真的会用百度吗?(百度搜索技巧-超详细)

热门文章

  1. 可以永久清除您的隐私——MacCleanse for Mac v8.0特别版垃圾清理软件!
  2. 调制解调系列(1) IQ调制(理论推导+工程实现(FM))
  3. 《电脑音乐制作实战指南:伴奏、录歌、MTV全攻略》——第2章 音频伴奏的获取与制作 2.1 获取伴奏的途径...
  4. Tauri vs. Electron:比较、操作方法和迁移指南
  5. Java安全--CC1的补充和CC6
  6. 那些年部署的服务器,cp2k环境部署安装
  7. Linux会帮你打剩下的字符,当您在命令提示符的后面输入命令的一部分时,按(__),Linux会帮你打剩下的字符,补充成为完整命令...
  8. 服务器进不了浏览器不支持,省考报名系统打开显示服务器进不去,该如何解决?...
  9. 树莓派VNC远程桌面使用,包含静态IP设置与窗口大小调整 (附软件链接)
  10. java版b2b2c o2o 多租户多商家电子商务之(商家管理)SpringCloud SpringBoot Mybatis Uniapp 分布式商城源码 电子商务源码 社交电商 直播带货