0、阅读本章需要哪些知识
我很少介绍关于基础的东西,因为这些文章太多了,网上关于基础的一抓一大把,可能比我介绍的还好,所以,在阅读本章前,需要具有基本的Socket通信流程、C语法、HTTP请求/响应格式、HTTP响应头字段各代表什么信息,这些就足够了。
一、什么是套接字
套接字其实叫socket,关于socket的文章以前写过一篇,是关于openjdk下的socket实现,里面介绍了java中socket底层的的实现方式,但是是在Window环境下,今天在Linux环境下做个演示,以及做个静态资源服务器。
在插入一个小知识点,Window窗口程序中如何不阻塞主线程实现socket数据读取?并不能使用多线程。
这似乎不好实现,在调用recv去接收数据的时候,一般情况下会阻塞,那么窗口程序就可能发白,那该怎么做呢?
在以前看过这样的一个写法,先不进行recv,而是等待Windows系统主动向程序发送一个消息,这个消息的标识具体已经记不清了,因为过去很多年了,然后在窗口过程中去判断消息,如果是,则在进行recv,这样就不会发生太长时间的阻塞。
简而言之就是单线程实现socket并发,在java中可以使用nio下的包,第一代的api中似乎没办法。
好了在说说什么是socket,有了socket,我们就可以在不同机器上的进程间通过网络进行通信,也可以在同一台机器中不同进程进行通信,通信,就是互相发数据,数据可以是字符,也可以是二进制数据,我们在使用浏览器的时候,浏览器就会和对应的服务器建立一个连接,建立连接后浏览器会构造一个http请求体,然后把数据发送到服务器中,服务器解析http请求体,并处理具体的数据,这些通常是服务器软件来完成的,比如tomcat,如果浏览器要获取一个静态资源,那么tomcat就直接处理了,如果是动态的,那么会交给servlet或jsp处理,处理之后在返回给浏览器。
服务器就需要创建一个称之为ServerSocket的东西(也是一个Socket,服务端的写法和客户端的写法稍微有点不一样),一直等待客户端连接,接下来我们简单写一个ServerSocket,然后通过浏览器去请求资源。
二、ServerSocket创建
下面创建的Socket在收到客户端连接后,读取并发送一串JSON数据,如果我们想源源不断的处理请求,就的写个循环。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
void handlerError(const char *msg) {
perror(msg);
exit(1);
}
int main(int argc, char *argv[]) {
int sockfd;
socklen_t clilen;
char buffer[256];
struct sockaddr_in serv_addr, cli_addr;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
handlerError("创建Socket错误");
bzero((char *) &serv_addr, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = INADDR_ANY;
serv_addr.sin_port = htons(8888);
if (bind(sockfd, (struct sockaddr *) &serv_addr,
sizeof(serv_addr)) < 0)
handlerError("服务绑定失败");
listen(sockfd, 5);
clilen = sizeof(cli_addr);
while (1){
int clientSocketFd= accept(sockfd,(struct sockaddr *) &cli_addr,&clilen);
bzero(buffer, 256);
read(clientSocketFd, buffer, 255);
printf("客户端消息: %s\n", buffer);
char* msg ="{\"code\":0,\"msg\":\"ok\"}";
write(clientSocketFd, msg, strlen(msg));
shutdown(clientSocketFd,SHUT_WR);
}
return 0;
}
每一个函数在下面这篇文章中也说了,就不解释了,虽然是在Window平台,但是都差不多一样
然后我们启动一个客户进行测试。
public class Main {
public static void main(String[] args) throws IOException {
InetSocketAddress inetSocketAddress = new InetSocketAddress(8888);
SocketChannel socketChannel = SocketChannel.open(inetSocketAddress);
ByteBuffer src = ByteBuffer.wrap("hello".getBytes());
/**
* 发送数据
*/
int write = socketChannel.write(src);
/**
* 读取数据
*/
ByteBuffer allocate = ByteBuffer.allocate(2048);
socketChannel.read(allocate);
allocate.flip();
byte[] msg = new byte[allocate.limit()];
allocate.get(msg);
System.out.println(new String(msg));
}
}
三、HTTP请求体
如果使用浏览器去访问的话,虽然会得到一个错误,但是服务器会收到一大串数据,这就证明了浏览器已经成功建立了一个socket连接,并向服务器发送了http请求体,只是服务端没能按照"约定"返回数据,浏览器没能解析,然后只能报错,通常浏览器会发送两个请求,一个请求是正文,用于访问具体的资源,一个是图标,在请求行头中是以GET /favicon.ico HTTP/1.1
这样表示的。
那么接下来就让服务端按照”约定“返回数据,看看效果。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
void handlerError(const char *msg) {
perror(msg);
exit(1);
}
int main(int argc, char *argv[]) {
int sockfd;
socklen_t clilen;
char buffer[256];
struct sockaddr_in serv_addr, cli_addr;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
handlerError("创建Socket错误");
bzero((char *) &serv_addr, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = INADDR_ANY;
serv_addr.sin_port = htons(8888);
if (bind(sockfd, (struct sockaddr *) &serv_addr,
sizeof(serv_addr)) < 0)
handlerError("服务绑定失败");
listen(sockfd, 5);
clilen = sizeof(cli_addr);
while (1){
int clientSocketFd= accept(sockfd,(struct sockaddr *) &cli_addr,&clilen);
bzero(buffer, 256);
read(clientSocketFd, buffer, 255);
printf("客户端消息: %s\n", buffer);
char* msg ="HTTP/1.1 200 OK\n"
"Connection: close\n"
"Content-Type: text/html\n"
"\n"
"{\"code\":0,\"msg\":\"ok\"}";
write(clientSocketFd, msg, strlen(msg));
shutdown(clientSocketFd,SHUT_WR);
}
return 0;
}
再次通过浏览器访问,这回浏览器能成功解析了。
四、传输文件
好了,接下来我们让他支持文件传输,大概分为三步:
- 解析HTTP行,取得用户访问的资源名称。
- 设置http响应头,设置文件名称、文件大小。
- 打开对应的文件,读取并发送。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <fcntl.h>
int getFileSize(char *filename) {
FILE *fp = fopen(filename, "r");
if (!fp) return -1;
fseek(fp, 0L, SEEK_END);
int size = ftell(fp);
fclose(fp);
return size;
}
char *substring(char *ch, int pos, int length) {
char *pch = ch;
char *subch = (char *) calloc(sizeof(char), length + 1);
int i;
pch = pch + pos;
for (i = 0; i < length; i++) {
subch[i] = *(pch++);
}
subch[length] = '\0';
return subch;
}
char *getRequestRes(char *ch) {
char *start = strchr(ch, '/') + 1;
char *end = strchr(start, ' ');
int startPos = strlen(ch) - strlen(start);
int resLength = strlen(start) - strlen(end);
return substring(ch, startPos, resLength);
}
void handlerError(const char *msg) {
perror(msg);
exit(1);
}
void *handlerClient(int clientSocketFd) {
char buffer[2048];
printf("处理客户端请求%d\n", clientSocketFd);
bzero(buffer, 2048);
int sRead =read(clientSocketFd, buffer, 2048);
if (sRead==-1){return 1;}
printf("数据%s", buffer);
char *resName = getRequestRes(buffer);
char *work = "/home/HouXinLin/test/";
char *path = (char *) malloc(strlen(resName)+strlen(work));
strcat(path, work);
strcat(path, resName);
int fSize = getFileSize(path);
printf("路径=%s,大小%d\n", path, fSize);
/**
* 构建HTTP相应头
*/
char *response;
response = (char *) malloc(400);
strcat(response, "HTTP/1.1 200 OK\n");
strcat(response, "Connection: close\nContent-Type: application/octet-stream;\n");
strcat(response, "Content-Disposition: attachment; filename=");
strcat(response, resName);
strcat(response, "\n");
strcat(response, "Content-Length:");
sprintf(response + strlen(response), "%d", fSize);
strcat(response, "\n\n");
printf("%s\n", response);
write(clientSocketFd, response, strlen(response));
free(response);
int resFD = open(path, O_RDONLY);
int readBuffer[2048];
int size = 0;
/**
* 发送资源数据
*/
while ((size = read(resFD, readBuffer, 2048)) > 0) {
write(clientSocketFd, readBuffer, size);
}
close(resFD);
shutdown(clientSocketFd, 1);
}
int main(int argc, char *argv[]) {
int sockfd;
struct sockaddr_in serv_addr, cli_addr;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
handlerError("创建Socket错误");
bzero((char *) &serv_addr, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);;
serv_addr.sin_port = htons(8888);
if (bind(sockfd, (struct sockaddr *) &serv_addr,
sizeof(serv_addr)) < 0)
handlerError("服务绑定失败");
listen(sockfd, 5);
while (1){
struct sockaddr_in client_addr;
char cli_ip[INET_ADDRSTRLEN] = "";
socklen_t cliaddr_len = sizeof(client_addr);
printf("等待连接\n");
int clientSocketFd;
clientSocketFd = accept(sockfd, (struct sockaddr*)&client_addr, &cliaddr_len);
if(clientSocketFd < 0)
{
perror("accept");
continue;
}
if (clientSocketFd==-1){continue;}
struct timeval tv;
tv.tv_sec = 3;
tv.tv_usec = 0;
setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
handlerClient(clientSocketFd);
}
return 0;
}
我们将工作目录设置到了/home/HouXinLin/test
下,在这个目录下有这些文件,然后我们试着通过浏览器访问他。
效果如下:
这段程序搞了我一天,因为对linux下c编程不算太熟,总结了一句话,c语言真TM难写。