1.1 TCP介绍、编程流程
TCP回顾: ? 1.面向连接的流式协议,可靠、出错重传、且每收到一个数据都要给出相应的确认; ? 2.通信之前需要建立链接; ? 3.服务器是被动链接,客户端是主动链接
TCP与UDP的差异: ?
TCP C/S架构
TCP编程流程
服务器: ? 创建套接字socket() ? 将套接字与服务器网络信息结构体绑定bind() ? 将套接字设置为监听状态listen() ? 阻塞等待客户端的连接请求accept()(阻塞函数) ? 进行通信recv()/send() ? 关闭套接字close()
客户端: ? 创建套接字socket() ? 发送客户端连接请求connect() ? 进行通信send()/recv() ? 关闭套接字close()
1.2 TCP编程—socket
#include <sys/types>
#include <sys/socket.h>
int socket(int domain,int type,int protocol);
功能:创建一个套接字,返回一个文件描述符
参数:
domain: 通信域,协议族
AF_UNIX 本地通信
AF_INET ipv4网络协议
AF_INET6 ipv6网络协议
AF_PACKET 底层接口
type: 套接字的类型
SOCK_STREAM 流式套接字(tcp)
SOCK_DGRAM 数据报套接字(udp)
SOCK_RAW 原始套接字
protocol: 附加协议,如果不需要,则设置为0
返回值:
成功: 文件描述符
失败: -1
案例:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
int main(int argc, char **argv){
int sockfd;
if((sockfd = socket(AF_INET,SOCK_STREAM,0)) == -1){
perror("fail to socket");
exit(1);
}
return 0;
}
1.3 connect、send、recv
1.3.1 connect函数
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd,const struct sockaddr* addr, socklen_t len);
功能: 给服务器发送客户端的连接请求
参数:
sockfd: 文件描述符,socket函数的返回值
addr: 要连接的服务器的网络信息结构以(需要自己设置)
addrlen: addr的长度
返回值:
成功:0
失败:-1
注意: ? 1.connect建立连接之后不会产生新的套接字 ? 2.连接成功后才可以开始传输TCP数据 ? 3.头文件:#include<sys/socket.h>
1.3.2 send函数
#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd,const void *buf,size_t len,int flags);
功能: 发送数据
参数:
sockfd: 文件描述符
客户端:socket函数的返回值
服务器:accept函数的返回值
buf: 发送的数据
len: buf的长度
flags: 标志位
0 阻塞
MSG_DONTWAIT 非阻塞
返回值:
成功:发送的字节数
失败:-1
注意: ? 不能用TCP协议发送0长度的数据包
1.3.3 recv函数
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recv(int sockfd,void *buf,size_t len,int flags);
功能: 接收数据
参数:
sockfd: 文件描述符
客户端:socket函数的返回值
服务器:accept函数的返回值
buf: 发送的数据
len: buf的长度
flags: 标志位
0 阻塞
MSG_DONTWAIT 非阻塞
返回值:
成功:接收的字节数
失败:-1
如果发送端关闭文件描述符或者关闭进程,则recv函数会返回0
1.3.4 客户端代码
使用windows下的网络调试助手作为服务器
客户端的代码:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet.in.h>
#include <string.h>
int main(int argc, char **argv){
if(argc < 3){
fprintf(stderr,"UseageL %s [ip] [port]\n",argv[0]);
}
int sockfd;
if((sockfd = socket(AF_INET,SOCK_STREAM,0)) == -1){
perror("fail to socket");
exit(1);
}
struct sockaddr_in serveraddr;
socklen_t addrlen = sizeof(serveraddr);
serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
serveraddr.sin_port = htons(atoi(agrv[2]));
if(connect(sockfd,(struct sockaddr *)&serveraddr,addrlen) == -1)
{
perror("fail to connect");
exit(1);
}
char buf[128] = "";
fgets(buf,128,stdin);
buf[strlen(buf) - 1] = '\0';
if(send(sockfd,buf,128,0) == -1){
perror("fail to send");
exit(1);
}
char text[128] = "";
if(recv(sockfd,text,128,0) == -1){
perror("fail to recv");
exit(1);
}
printf("from server: %s\n",text);
close(sockfd);
return 0;
}
1.4 TCP服务器—bind、listen、accept
1.4.1 作为TCP服务器需要具备的条件
1.具备一个可以确知的地址; 2.让操作系统知道是一个服务器,而不是客户端; 3.等待连接的到来
对于面向连接的TCP协议来说,连接的建立才真正意味着数据通信的开始。
1.4.2 bind函数
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd,const struct sockaddr *addr,socklen_t addrlen);
功能:将套接字与网络信息结构体绑定
参数:
sockfd:文件描述符,socket的返回值
addr: 网络信息结构体
addrlen:addrlen的长度
返回值:
成功: 0
失败: -1
1.4.3 listen函数
#include <sys/types.h>
#include <sys/socket.h>
int listen(int sockfd,int backlog);
功能:
将套接字由主动修改为被动
使操作系统为该套接字设置一个连接队列,用来记录所有连接到该套接字的连接
参数:
sockfd: socket监听套接字
backlog:连接队列的长度
返回值:
成功:返回0
失败:其他
1.4.4 accept函数
#include <sys/types.h>
#include <sys/socket.h>
int accept(int sockfd,struct sockaddr *cliaddr,socklen_t *addrlen);
功能:
从已连接队列中取出一个已经建立的连接,如果没有任何连接可用,则进入睡眠等待(阻塞)
参数:
sockfd: socket监听套接字
cliaddr:用于存放客户端套接字地址结构
addrlen:套接字地址结构体长度的地址
返回值:
成功:新的文件描述符(只要由客户端连接,就会产生新的文件描述符,这个新的文件描述符专门与指定的客户端进行通信的)
失败:-1
注意:返回的是一个已连接套接字,这个套接字代表当前这个连接
1.4.5 TCP服务器例子
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
int main(int argc, char **argv)
{
if(argc < 3){
fprintf(stderr,"UseageL %s [ip] [port]\n",argv[0]);
}
int sockfd;
if((sockfd = socket(AF_INET,SOCK_STREAM,0)) == -1){
perror("fail to socket");
exit(1);
}
struct sockaddr_in serveraddr;
socklen_t addrlen = sizeof(serveraddr);
serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
serveraddr.sin_port = htons(atoi(argv[2]));
if(bind(sockfd,(struct sockaddr *)&serveraddr,addrlen) == -1)
{
perror("fail to bind");
exit(1);
}
if(listern(sockfd,10) == -1){
perror("fail to listen");
exit(1);
}
int acceptfd;
struct sockaddr_in clientaddr;
if((acceptfd = accept(sockfd,(struct sockaddr *)&clientaddr,&addrlen)) == -1){
perror("fail to accept");
exit(1);
}
printf("ip:%s ,port:%d\n",inet_ntoa(clientaddr),ntohs(clientaddr.sin_port));
char buf[128] = "";
if(recv(acceptfd,buf,128,0) == -1){
perror("fail to recv");
exit(1);
}
printf("from client: %s\n",buf);
strcat(buf,"*_*");
if(send(acceptfd,buf,128,0) == -1){
perror("fail to send");
exit(1);
}
return 0;
}
1.5 TCP编程—close、三次握手、四次挥手
1.5.1 close关闭套接字
1.使用close函数即可关闭套接字(关闭一个代表已连接套接字将导致另一端接收到一个0长度的数据包)
2.做服务器时 ? 1>关闭监听套接字将导致服务器无法接收到新的连接,但不会影响已经建立的连接 ? 2>关闭accept返回的已连接套接字将导致他所代表的连接被关闭,但不会影响服务器的监听
3.做客户端时
? 关闭连接就是关闭连接,不意味着其他
1.5.2 三次握手
1.5.3 四次挥手
1.6 TCP并发服务器
TCP原本不是并发服务器,TCP服务器同一时间只能与一个客户端通信。
原始代码:
client.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet.in.h>
#include <string.h>
int main(int argc, char **argv){
if(argc < 3){
fprintf(stderr,"UseageL %s [ip] [port]\n",argv[0]);
}
int sockfd;
if((sockfd = socket(AF_INET,SOCK_STREAM,0)) == -1){
perror("fail to socket");
exit(1);
}
struct sockaddr_in serveraddr;
socklen_t addrlen = sizeof(serveraddr);
serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
serveraddr.sin_port = htons(atoi(agrv[2]));
if(connect(sockfd,(struct sockaddr *)&serveraddr,addrlen) == -1)
{
perror("fail to connect");
exit(1);
}
char buf[128] = "";
while(1){
fgets(buf,128,stdin);
buf[strlen(buf) - 1] = '\0';
if(send(sockfd,buf,128,0) == -1){
perror("fail to send");
exit(1);
}
if(strncmp(buf,"quit",4) == 0){
exit(0);
}
char text[128] = "";
if(recv(sockfd,text,128,0) == -1){
perror("fail to recv");
exit(1);
}
printf("from server: %s\n",text);
}
close(sockfd);
return 0;
}
server.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
int main(int argc, char **argv)
{
if(argc < 3){
fprintf(stderr,"UseageL %s [ip] [port]\n",argv[0]);
}
int sockfd;
if((sockfd = socket(AF_INET,SOCK_STREAM,0)) == -1){
perror("fail to socket");
exit(1);
}
int on = 1;
if(setsockopt(sockfd,SOL_SOCKET,SO)REUSEADDR,&on,sizeof(on) < 0){
perror("fail to setsockopt");
exit(1);
}
struct sockaddr_in serveraddr;
socklen_t addrlen = sizeof(serveraddr);
serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
serveraddr.sin_port = htons(atoi(argv[2]));
if(bind(sockfd,(struct sockaddr *)&serveraddr,addrlen) == -1)
{
perror("fail to bind");
exit(1);
}
if(listern(sockfd,10) == -1){
perror("fail to listen");
exit(1);
}
int acceptfd;
struct sockaddr_in clientaddr;
if((acceptfd = accept(sockfd,(struct sockaddr *)&clientaddr,&addrlen)) == -1){
perror("fail to accept");
exit(1);
}
printf("ip:%s ,port:%d\n",inet_ntoa(clientaddr),ntohs(clientaddr.sin_port));
char buf[128] = "";
ssize_t bytes;
while(1){
if(bytes = recv(acceptfd,buf,128,0) < 0){
perror("fail to recv");
exit(1);
}
else if(bytes == 0){
printf("the clinet quited\n");
exiy(1);
}
if(strncmp(buf,"quit",4) == 0){
exit(0);
}
printf("from client: %s\n",buf);
strcat(buf,"*_*");
if(send(acceptfd,buf,128,0) == -1){
perror("fail to send");
exit(1);
}
}
return 0;
}
TCP不能实现并发的原因: 由于TCP服务器端有两个读阻塞函数,accept和recv,两个函数需要先后运行,所以导致运行一个函数的时候另一个函数无法执行,所以无法保证一边连接客户端,一边与其他客户端通信。
如何实现TCP并发服务器: 1.使用多进程实现TCP并发服务器 2.使用多线程实现TCP并发服务器
1.6.1 多进程实现并发
int sockfd = socket();
bind()
listen()
while(1)
{
accept()
pid = fork();
if(pid > 0)
{
}
else if(pid == 0)
{
while(1)
{
recv()/send()
}
}
}
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
#include <signal.h>
#include <sys/wait.h>
#define N 128
#define ERR_LOG(errmsg)do{\ perror(errmsg);\
exit(1);\
}while(0)
void handler(int sig){
wait(NULL);
}
int main(int argc, char **argv)
{
if(argc < 3){
fprintf(stderr,"UseageL %s [ip] [port]\n",argv[0]);
}
int sockfd;
if((sockfd = socket(AF_INET,SOCK_STREAM,0)) == -1){
ERR_LOG("fail to socket");
}
int on = 1;
if(setsockopt(sockfd,SOL_SOCKET,SO)REUSEADDR,&on,sizeof(on) < 0){
ERR_LOG("fail to setsockopt");
}
struct sockaddr_in serveraddr;
socklen_t addrlen = sizeof(serveraddr);
serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
serveraddr.sin_port = htons(atoi(argv[2]));
if(bind(sockfd,(struct sockaddr *)&serveraddr,addrlen) == -1)
{
ERR_LOG("fail to bind");
}
if(listern(sockfd,10) == -1){
perror("fail to listen");
exit(1);
}
signal(SIGCHID,handler)
while(1){
int acceptfd;
struct sockaddr_in clientaddr;
if((acceptfd = accept(sockfd,(struct sockaddr *)&clientaddr,&addrlen)) == -1){
perror("fail to accept");
exit(1);
}
printf("ip:%s ,port:%d\n",inet_ntoa(clientaddr),ntohs(clientaddr.sin_port));
pid_t pid;
if((pid = fork()) < 0){
ERR_LOG("fail to fork");
}
else if(pid > 0){
}
else{
char buf[128] = "";
ssize_t bytes;
while(1){
if(bytes = recv(acceptfd,buf,128,0) < 0){
perror("fail to recv");
exit(1);
}
else if(bytes == 0){
printf("the clinet quited\n");
exit(1);
}
if(strncmp(buf,"quit",4) == 0){
exit(0);
}
printf("from client: %s\n",buf);
strcat(buf,"*_*");
if(send(acceptfd,buf,128,0) == -1){
perror("fail to send");
exit(1);
}
}
}
}
return 0;
}
1.6.2 多线程并发实现
void *thread_fun(void *arg){
while(1){
recv()/send()
}
}
sockfd = socket()
bind()
listen()
while(1)
{
accept()
pthread_create(&thread,NULL,thread_fun,...)
pthread_detach();
}
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
#include <signal.h>
#include <sys/wait.h>
#define N 128
#define ERR_LOG(errmsg)do{\ perror(errmsg);\
exit(1);\
}while(0)
typedef struct{
struct sockaddr_in addr;
int acceptfd;
}MSG;
void *pthread_fun(void *arg){
char buf[N] = "";
ssize_t bytes;
MSG msg = *(MSG *)arg;
while(1){
if(bytes = recv(msg.acceptfd,buf,128,0) < 0){
perror("fail to recv");
exit(1);
}
else if(bytes == 0){
printf("the clinet quited\n");
exit(1);
}
if(strncmp(buf,"quit",4) == 0){
printf("The client quited\n");
pthread_exit(NULL);
}
printf("[%s - %d]: %s\n",inet_ntoa(msg.addr.sin_addr),ntohs(msg.addr.sin_port),buf);
strcat(buf,"*_*");
if(send(msg.acceptfd,buf,128,0) == -1){
perror("fail to send");
exit(1);
}
}
}
int main(int argc, char **argv)
{
if(argc < 3){
fprintf(stderr,"UseageL %s [ip] [port]\n",argv[0]);
}
int sockfd;
if((sockfd = socket(AF_INET,SOCK_STREAM,0)) == -1){
ERR_LOG("fail to socket");
}
int on = 1;
if(setsockopt(sockfd,SOL_SOCKET,SO)REUSEADDR,&on,sizeof(on) < 0){
ERR_LOG("fail to setsockopt");
}
struct sockaddr_in serveraddr;
socklen_t addrlen = sizeof(serveraddr);
serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
serveraddr.sin_port = htons(atoi(argv[2]));
if(bind(sockfd,(struct sockaddr *)&serveraddr,addrlen) == -1)
{
ERR_LOG("fail to bind");
}
if(listern(sockfd,10) == -1){
perror("fail to listen");
exit(1);
}
signal(SIGCHID,handler)
while(1){
int acceptfd;
struct sockaddr_in clientaddr;
if((acceptfd = accept(sockfd,(struct sockaddr *)&clientaddr,&addrlen)) == -1){
perror("fail to accept");
exit(1);
}
printf("ip:%s ,port:%d\n",inet_ntoa(clientaddr),ntohs(clientaddr.sin_port));
MSG msg;
msg.addr = clientaddr;
msg.acceptfd = acceptfd;
pthread_t thread;
if(pthread_create(&thread,NULL,pthread_fun,&msg) != 0){
ERR_LOG("fail to pthread_create");
}
pthread_detach(thread);
}
}
return 0;
}
1.7 Web服务器介绍
1.7.1 web服务器简介
Web服务器又称WWW服务器、网站服务器等
特点: 使用HTTP协议与客户机浏览器进行信息交流 不仅能存储信息,还能在用户通过web浏览器提供的信息的基础上运行脚本和程序 该服务器可安装在UNIX、Linux或Windows等操作系统上 著名的服务器有Apache、Tomcat、IIS等
1.7.2 HTTP协议
Webserver—HTTP协议(超文本协议)
概念:一种详细规定了浏览器和万维网服务器之间互相通信的规则,通过因特网传送万维网文档的数据传送协议
特点: 1.支持C/S架构 2.简单快速:客户向服务器请求服务时,只需传送请求方法和路径,常用方法:GET、POST 3.无连接:限制每次连接只处理一个请求
1.7.3 Webserver通信过程
1.7.4 Web编程开发
网页浏览(使用过GET方式)
客户端浏览请求
Web服务器的ip地址是192.168.3.103,端口号是9999,要访问的网页时about.html
服务器收到的数据:
GET/index.html HTTP/1.1
Accept:image/gif.image/jpeg,*
服务器应答的格式:
服务器接收到浏览器发送的数据之后,需要判断GET/后面跟的网页是否存在,如果存在则请求成功,发送指定的指令,并发送文件内容给浏览器,如果不存在,则发送请求失败的指令
请求成功
"HTTP/1.1 200 OK\r\n" \
"Content-Type: text/html\r\n" \
"\r\n";
请求失败
"HTTP/1.1 404 Not Found\r\n" \
"Content-Type: text/html\r\n" \
"\r\n" \
"<HTML><BODY>File not found</BODY></HTML>"
案例:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
#include <signal.h>
#include <sys/wait.h>
#define N 128
#define ERR_LOG(errmsg)do{\ perror(errmsg);\
exit(1);\
}while(0)
void *pthread_fun(void *arg){
int acceptfd = *(int *)arg;
char buf[N] = "";
char head[] = "HTTP/1.1 200 OK\r\n" \
"Content-Type: text/html\r\n" \
"\r\n";
char err[] = "HTTP/1.1 404 Not Found\r\n" \
"Content-Type: text/html\r\n" \
"\r\n" \
"<HTML><BODY>File not found</BODY></HTML>";
if(recv(acceptfd,buf,N,0) < 0){
ERR_LOG("fail to recv");
}
printf("******************\n\n");
printf("%s\n",buf);
char filename[128] = "";
sscanf(buf,"GET /%s",filename);
if(strncmp(filename,"HTTP/1.1",strlen("http/1.1")) == 0){
strcpy(filename,"about.html");
}
printf("filename = %s\n",filename);
char path[128] = "./sqlite/";
strcat(path,filename);
int fd;
if((fd = open(path,O_RDONLY)) < 0){
if(errno == ENOENT)
{
if(send(acceptfd,err,strlen(err),0) < 0){
ERR_LOG("fail to send");
}
close(acceptfd);
pthread_exit(NULL);
}
else{
ERR_LOG("fail to open");
}
}
if(send(acceptfd,head,strlen(head),0) < 0){
ERR_LOG("fail to send");
}
ssize_t bytes;
char text[1024] = "";
while((bytes = read(fd,text,1024)) > 0){
if(send(acceptfd,text,bytes,0) < 0){
ERR_LOG("fail to send");
}
}
pthread_exit(NULL);
}
int main(int argc, char **argv)
{
if(argc < 3){
fprintf(stderr,"UseageL %s [ip] [port]\n",argv[0]);
}
int sockfd;
if((sockfd = socket(AF_INET,SOCK_STREAM,0)) == -1){
ERR_LOG("fail to socket");
}
int on = 1;
if(setsockopt(sockfd,SOL_SOCKET,SO)REUSEADDR,&on,sizeof(on) < 0){
ERR_LOG("fail to setsockopt");
}
struct sockaddr_in serveraddr;
socklen_t addrlen = sizeof(serveraddr);
serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
serveraddr.sin_port = htons(atoi(argv[2]));
if(bind(sockfd,(struct sockaddr *)&serveraddr,addrlen) == -1)
{
ERR_LOG("fail to bind");
}
if(listern(sockfd,10) == -1){
perror("fail to listen");
exit(1);
}
signal(SIGCHID,handler)
while(1){
int acceptfd;
struct sockaddr_in clientaddr;
if((acceptfd = accept(sockfd,(struct sockaddr *)&clientaddr,&addrlen)) == -1){
perror("fail to accept");
exit(1);
}
printf("ip:%s ,port:%d\n",inet_ntoa(clientaddr),ntohs(clientaddr.sin_port));
MSG msg;
msg.addr = clientaddr;
msg.acceptfd = acceptfd;
pthread_t thread;
if(pthread_create(&thread,NULL,pthread_fun,&msg) != 0){
ERR_LOG("fail to pthread_create");
}
pthread_detach(thread);
}
}
return 0;
}
|