linux c套接字编程实例,Linux下C套接字编程,静态资源服务器实现

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 

#include 

#include 

#include 

#include 

#include 

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 

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)) 

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));

}

}

47c819fbc2faf1cb7894f7dbc3743cc0.gif

三、HTTP请求体

如果使用浏览器去访问的话,虽然会得到一个错误,但是服务器会收到一大串数据,这就证明了浏览器已经成功建立了一个socket连接,并向服务器发送了http请求体,只是服务端没能按照"约定"返回数据,浏览器没能解析,然后只能报错,通常浏览器会发送两个请求,一个请求是正文,用于访问具体的资源,一个是图标,在请求行行中是以GET /favicon.ico HTTP/1.1这样表示的。

9aa2375124333be4b7f38cb6a082ea79.png

ce22480c1047d8554447ee6e5a37ace0.png

那么接下来就让服务端按照”约定“返回数据,看看效果。#include 

#include 

#include 

#include 

#include 

#include 

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 

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)) 

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;

}

再次通过浏览器访问,这会浏览器能成功解析了。

019fdba365aecd7dbf4d14e4cf48242c.png

四、传输文件

好了,接下来我们让他支持文件传输,大概分为三步:解析HTTP行,取得用户访问的资源名称,这部分在请求行中。

设置http响应头,就是设置文件名称、文件大小。

打开对应的文件,读取并发送。#include 

#include 

#include 

#include 

#include 

#include 

#include 

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 

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 

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)) 

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 

{

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下,在这个目录下有这些文件,然后我们试着通过浏览器访问他。

4726d2cb751739f13d1ac292954a8fbf.png

效果如下:

1cc1993aeb3567e480cc1ca38b477c1d.gif

这段程序搞了我一天,因为对linux下c编程不算太熟,总结了一句话,c语言真TM难写。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值