这一部分主要是先把现有代码讲述一下方便后面的博客描写
ps:我也是初学者,不保证代码的可靠性,只是能用,现有代码的问题也许会在后面修正,这取决于个人的学习深度,由于是学习向的,不采用任何的第三方库
运行在Linux环境下
首先来个单线程的
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdio.h>
int main()
{
int server_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);//IPV4+TCP
struct sockaddr_in server_addr;
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(8080);//监听端口
bind(server_fd, (struct sockaddr*)&server_addr, sizeof(struct sockaddr));
listen(server_fd, 1);
int len = sizeof(struct sockaddr);
struct sockaddr_in client_addr;
int client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &len);
char recvBuf[1024];
recv(client_fd, recvBuf, sizeof(recvBuf), 0);//接收浏览器发过来的请求
char sendBuf[]="HTTP/1.1 200 OK\r\nServer:BlogServer\r\nContent-Length:18\r\n\r\n<p>hello world</p>";//包括http响应头和正文<p>hello world</p>
send(client_fd, sendBuf, sizeof(sendBuf), 0);//不对请求分析统一返回上面的字符串
shutdown(client_fd, SHUT_RDWR);//关闭
close(client_fd);
close(server_fd);
return 0;
}
然后通过gcc编译一下
运行
通过chrome打开可以看到hello world
响应信息:
由于这个程序并不是多线程,也没有做循环处理,所以该程序只能运行一次就会退出
然而再次打开的时候却无法继续使用,为什么呢?
首先使用netstat -an看一下端口的占用情况,然而并没有发现8080端口
但是有个看起来比较可疑的http-alt
![Unnamed QQ Screenshot20150821181149 Unnamed QQ Screenshot20150821181149](http://static.oschina.net/uploads/img/201508/21220239_b8z9.png)
LISTEN:侦听来自远方的TCP端口的连接请求
SYN-SENT:再发送连接请求后等待匹配的连接请求
SYN-RECEIVED:再收到和发送一个连接请求后等待对方对连接请求的确认
ESTABLISHED:代表一个打开的连接
FIN-WAIT-1:等待远程TCP连接中断请求,或先前的连接中断请求的确认
FIN-WAIT-2:从远程TCP等待连接中断请求
CLOSE-WAIT:等待从本地用户发来的连接中断请求
CLOSING:等待远程TCP对连接中断的确认
LAST-ACK:等待原来的发向远程TCP的连接中断请求的确认
TIME-WAIT:等待足够的时间以确保远程TCP接收到连接中断请求的确认
CLOSED:没有任何连接状态
查一下资料发现TIME-WAIT是等待远程tcp的确认
经查询发现可以采用端口复用的方式来避免服务器重启等情况
int val = 1;
setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(int));
setsockopt(server_fd, SOL_SOCKET, SO_REUSEPORT, &val, sizeof(int));
这时候可以重复地连续地进行启动操作
如果让两个终端同时开启程序,第一个开启的(pid比较小的?)会先执行accept()
然后我们再进行并发的编程
#include <pthread.h>
添加必要的头
int main(int argc,char* argv[])
{
if(argc != 2) return 1;
int server_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
printf("socket_fd:%d\n", server_fd);
struct sockaddr_in server_addr;
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(atoi(argv[1]));
int val = 1;
setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(int));
setsockopt(server_fd, SOL_SOCKET, SO_REUSEPORT, &val, sizeof(int));
if(!bind(server_fd, (struct sockaddr*)&server_addr, sizeof(struct sockaddr))) {char buf[30];printf("bind:%s:%d\n",inet_ntop(AF_INET, &server_addr.sin_addr, buf, sizeof(buf)),(unsigned int)ntohs(server_addr.sin_port));}
else return 1;
if(!listen(server_fd, 1000)) {char buf[30];printf("listen:%s:%d\n",inet_ntop(AF_INET, &server_addr.sin_addr, buf, sizeof(buf)),(unsigned int)ntohs(server_addr.sin_port));}
else return 1;
//accept()将在线程中完成
pthread_t ntid;
pthread_create(&ntid, NULL, ListenClient, &server_fd);
//主线程开始等待输入
int loop;
scanf("%d",&loop);
close(server_fd);
return 0;
}
监听线程
void *ListenClient(void *server)
{
int server_fd = *(int*)server;
int len = sizeof(struct sockaddr);
struct sockaddr_in client_addr;
pthread_t ntid;
while(1)
{
int client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &len);
//printf("client_fd:%d\n", client_fd);
char buf[30];
printf("connect:%s:%d\n",inet_ntop(AF_INET, &client_addr.sin_addr, buf, sizeof(buf)),(unsigned int)ntohs(client_addr.sin_port));
//printf("%lu:start new response\n",(unsigned long)pthread_self());
//printf("%d\n",client_fd);
pthread_create(&ntid, NULL, HttpResponse, &client_fd);//每一次响应都创建新的线程来处理
pthread_detach(ntid);
}
}
响应线程
void *HttpResponse(void *client)
{
int client_fd = *(int*)client;
char recvBuf[1024];
//printf("%lu:start recv\n",(unsigned long)pthread_self());
recv(client_fd, recvBuf, sizeof(recvBuf), 0);
//printf("%s\n",recvBuf);
char sendBuf[]="HTTP/1.1 200 OK\r\nServer:BlogServer\r\nContent-Length:18\r\n\r\n<p>hello world</p>";
//printf("%lu:start send\n",(unsigned long)pthread_self());
send(client_fd, sendBuf, sizeof(sendBuf), 0);
shutdown(client_fd, SHUT_RDWR);
//printf("%lu:start close\n",(unsigned long)pthread_self());
close(client_fd);
pthread_exit((void *)0);
}
编译
gcc main.c -o httpserver -lpthread
这时候可以不停刷新浏览器运行了,还是很不错的
但此时系统还是不完善的,接着看
先用ps a看一下进程pid
再用top命令来观察(top –p 1431)
使用py脚本测试,这个比较简单
#coding=utf-8
import urllib
import thread
import time
i = 0
while 1:
try:
url = 'http://127.0.0.1:8080'
res = urllib.urlopen(url)
s = res.read()
res.close()
i = i + 1
if i % 5 == 0:
print str(i)+':'+s
except Exception as e:
print str(e)
在高速地循环运行下,系统依然是很稳健的,毕竟这个简单,但是这是在网络状况极好的状况下,那在网络异常等一下故障情况下,出现超时了是否能处理呢?
import socket
host='127.0.0.1'
port=8080
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect((host,port))
s.send('hello')
print s.recv(1024)
s.close
简单的一次socket连接
成功
import socket
host='127.0.0.1'
port=8080
while 1:
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect((host,port))
s.send('hello')
s.recv(1024)
s.close
循环,这时候跟http版本的相似,也是可以正常运行的
这时候要是把recv和close去掉,那么情况就不用乐观了,服务器几乎1秒就挂了
import socket
import time
host='127.0.0.1'
port=8080
while 1:
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect((host,port))
s.send('hello')
time.sleep(0.01)
#s.recv(1024)
#s.close
加上time.sleep(0.001)会缩短崩溃时间,但也不容乐观
问题出在哪里呢?大量的超时连接导致服务器资源过多奔溃吗?加入对客户端超时发生与接收的处理
struct timeval timeout = {5, 0};
setsockopt(client_fd,SOL_SOCKET,SO_RCVTIMEO,(char*)&timeout,sizeof(struct timeval));
setsockopt(client_fd,SOL_SOCKET,SO_SNDTIMEO,(char*)&timeout,sizeof(struct timeval));
实际上就算说对超时时间,短时间大量这样的连接还是会奔溃,问题到底出在哪里呢?
也晚了,等下一篇博客继续研究吧