一.网络基础知识
1. TCP状态转换图
主动连接端:CLOSED --> SYN_SENT --> ESTABLISHED
被动连接端:CLOSED --> LISTEN --> SYN_RCVD --> ESTABLISHED
主动关闭端:ESTABLISHED --> FIN_WAIT_1 --> FIN_WAIT_2(半关闭)–> TIME_WAIT [2MSL超时]–> CLOSED
被动关闭端:ESTABLISHED --> CLOSE_WAIT(对端处于半关闭) --> LAST_ACK -->CLOSED
2MSL:确保最后ACK被成功接收
查询状态: netsata -apn
2.C/S,B/S模型
browser-server(浏览器)
client-server(客户端)
类型 | C/S | B/S |
---|---|---|
优点 | 缓存大量数据,可自定义协议, 效率高速度快 | 安全性相对低,不能跨平台,开发工作量相当较大 |
缺点 | 安全性相对高,跨平台,开发工作量相对较小 | 不能缓存大量数据,严格遵守http协议 |
3.端口复用
#include <sys/types.h>
#include <sys/socket.h>
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
int opt = 1;
setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
二.并发服务器
1.多进程
父进程用来监听客户端的连接请求,每当一个客户端发来连接请求时父进程为其创建一个进程和该客户端进行某些操作以达到并发效果。
1.基本流程
socket,bind,listen --> accept --> fork
父进程 --> signal/sigaction --> accept
子进程 -->read -->write
注意:1.父子进程都要关闭相对的描述符再进行操作
2.如果父进程调用wait回收子进程时父进程会阻塞等待,所以调用waitpid, 参数option填宏WNOHANG,使得父进程不阻塞在回收上,但需要子进程结束的信号(SIGCHLD)来通知父进程回收
2.简单应用
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <signal.h>
#include <wait.h>
#include <string.h>
void catch_child(int signum)
{
while((waitpid(0, NULL,WNOHANG))>0);
return ;
}
int main()
{
struct sockaddr_in caddr, saddr;
int cfd, sfd;
socklen_t clen = sizeof(caddr);
pid_t pid;
int ret;
char buf[BUFSIZ], str[INET_ADDRSTRLEN];;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(9999);
saddr.sin_addr.s_addr = htonl(INADDR_ANY);
sfd = socket(AF_INET, SOCK_STREAM, 0);
bind(sfd, (struct sockaddr *)&saddr, sizeof(saddr));
listen(sfd, 128);
while(1)
{
cfd = accept(sfd, (struct sockaddr *)&caddr, &clen);
printf("connection -- port:%d\tip:%s\n", ntohs(caddr.sin_port), inet_ntop(AF_INET, &caddr.sin_addr.s_addr, str, sizeof(str)));
pid = fork();
switch (pid){
case 0:
close(sfd);
while(1)
{
ret = read(cfd, buf, sizeof(buf));
if(ret == 0)
{
printf("connection terminated.\n");
break;
}
else
{
for(int i=0; i<ret; i++)
buf[i] = toupper(buf[i]);
write(cfd, buf, ret);
write(STDOUT_FILENO, buf, ret);
}
}
break;
case -1:
perror("fork");
exit(1);
default:
{
struct sigaction act;
act.sa_handler = catch_child;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
sigaction(SIGCHLD, &act, NULL);
close(cfd);
continue;
}
}
}
return 0;
}
2.多线程
跟多进程并发服务器类似,主线程用来监听客户端连接请求,每当一个客户端发出连接请求时主线程创建一个副线程和该客户端进行某些操作以达到并发效果。
1.基本流程
socket,bind,listen --> accept --> pthread_create
主线程 --> pthread_detach --> accept
副线程 --> read --> write
注意:因为直接pthread_join回收副线程资源时会阻塞主线程,所以通过pthread_detach分离副线程,等副线程结束时自动释放资源而不使用pthread_join
2.简单应用
#include <stdio.h>
#include <pthread.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
void *obj(void *arg)
{
int n, j;
int *cfd = (int*)arg;
char buf[BUFSIZ];
while(1)
{
n = read(*cfd, buf, sizeof(buf));
if(n == 0)
{
printf("connection terminated.\n");
break;
}
else
{
for(j = 0; j < n; j++)
buf[j] = toupper(buf[j]);
write(STDOUT_FILENO, buf, n);
write(*cfd, buf, n);
}
}
close(*cfd);
return (void *)0;
}
int main()
{
int lfd, cfd;
socklen_t clen;
char str[INET_ADDRSTRLEN];
struct sockaddr_in caddr, saddr;
bzero(&saddr, sizeof(saddr));
saddr.sin_family = AF_INET;
saddr.sin_port = htons(7777);
saddr.sin_addr.s_addr = htonl(INADDR_ANY);
lfd = socket(AF_INET, SOCK_STREAM, 0);
bind(lfd, (struct sockaddr *)&saddr, sizeof(saddr));
listen(lfd, 128);
pthread_t tid;
while(1)
{
clen = sizeof(caddr);
cfd = accept(lfd, (struct sockaddr *)&caddr, &clen);
printf("connection -- port:%d\tip:%s\n", ntohs(caddr.sin_port), inet_ntop(AF_INET, &caddr.sin_addr.s_addr, str, sizeof(str)));
pthread_create(&tid, NULL, obj, (void *)&cfd);
pthread_detach(tid);
}
return 0;
}