wgf1209 的学生作业:
tcp_server.h
#ifndef __SERVER_H__
#define __SERVER_H__
#include
#include
#include
#include
#include
#include
#include
#include
#include
/*
@Author: wjhu8
@Time: 2024-10-30 21:29:32
@name: tcp_server.h
*/
#define STD_HEADER "Connection: close\r\n" \
"Server: MJPG-Streamer/0.2\r\n" \
"Cache-Control: no-store, no-cache, must-revalidate, pre-check=0, post-check=0, max-age=0\r\n" \
"Pragma: no-cache\r\n" \
"Expires: Mon, 3 Jan 2013 12:34:56 GMT\r\n"
#define BOUNDARY "cyg-boundary"
#define WEB_DIR "www"
typedef enum {
A_SNAPSHOT, //快照--截图
A_STREAM, //流---视频
A_FILE, //文件
} answer_t;
//HTTP请求
typedef struct {
answer_t type;
char *parm;
} request_t;
//1.创建 tcp server socket
extern int create_tcp_server_socket(const char *ip, const char *port);
//2.等待并接受客户端连接,返回新的连接套接字文件描述符。
extern int accept_client_connect(int sockfd, struct sockaddr_in *cli_addr);
//3.显示连接客服端的地址信息
extern void show_tcp_network_address(struct sockaddr_in *sockaddr);
extern void *client_thread(void *arg);
extern int analyse_http_request(const char *buf, request_t *req);
extern void send_file(int sockfd, char *file_path);
extern void send_stream(int sockfd);
extern void send_snapshot(int sockfd);
#endif //__SERVER_H__
tcp_server.c
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "../head/debug.h"
#include "../head/tcp_server.h"
#include "../head/global.h"
/*
@Author: wjhu8
@Time: 2024-10-30 21:29:32
@name: tcp_server.c
*/
//自定义宏 ----> 监听队列的长度
#define BACKLOG 10
static const struct {
const char *dot_extension;
const char *mimetype;
} mimetypes[] = {
{".html", "text/html"},
{".htm", "text/html"},
{".css", "text/css"},
{".js", "text/javascript"},
{".txt", "text/plain"},
{".jpg", "image/jpeg"},
{".jpeg", "image/jpeg"},
{".png", "image/png"},
{".gif", "image/gif"},
{".ico", "image/x-icon"},
{".swf", "application/x-shockwave-flash"},
{".cab", "application/x-shockwave-flash"},
{".jar", "application/java-archive"}
};
global_t global;
//初始化套接字
int init_tcp(const char *ipstr, unsigned short port, int backlog) {
int sfd;
//1.创建套接字
sfd = socket(AF_INET, SOCK_STREAM, 0);
if (sfd == -1) {
DEBUG_INFO("[ERROR] Failed to socket: %s\n", strerror(errno));
return -1;
}
int on = 1;
if (0 > setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on))) {
DEBUG_INFO("[ERROR] Failed to setsockopt: %s\n", strerror(errno));
return -1;
}
struct sockaddr_in addr = {
// 初始化服务器地址结构体对象
.sin_family = AF_INET, //协议族----> AF_INTE
.sin_port = htons(port), //端口
.sin_addr.s_addr = htons(INADDR_ANY), //IP地址
.sin_zero={0},
};
//2.绑定服务端本身的ip和port到socket
if (0 > bind(sfd, (const struct sockaddr *) &addr, sizeof(addr))) {
DEBUG_INFO("[ERROR] Failed to bind: %s\n", strerror(errno));
return -1;
}
listen(sfd, backlog);
printf("Listen sfd %d port %d\n", sfd, port);
printf("server ip is %s\n", inet_ntoa(addr.sin_addr));
return sfd;
}
//函数作用:创建 tcp server socket
int create_tcp_server_socket(const char *ip, const char *port) {
int sfd, ret;
struct sockaddr_in svr_addr;
//1.创建套接字
sfd = socket(AF_INET, SOCK_STREAM, 0);
if (sfd == -1) {
DEBUG_INFO("[ERROR] Failed to socket: %s\n", strerror(errno));
return -1;
}
//将 svr_addr 结构体变量所占用的内存区域全部设置为零
bzero(&svr_addr, sizeof(struct sockaddr_in));
// 初始化服务器地址结构体对象
svr_addr.sin_family = AF_INET; //协议族----> AF_INTE
svr_addr.sin_port = htons(atoi(port)); //端口
svr_addr.sin_addr.s_addr = inet_addr(ip); //IP地址
//2.绑定服务端本身的ip和port到socket
ret = bind(sfd, (const struct sockaddr *) &svr_addr, sizeof(struct sockaddr_in));
if (ret == -1) {
DEBUG_INFO("[ERROR] Failed to bind: %s\n", strerror(errno));
return -1;
}
//3.将套接字设置为监听状态并创建监听队列
ret = listen(sfd, BACKLOG); //BACKLOG(监听队列的长度) 10
if (ret == -1) {
DEBUG_INFO("[ERROR] Failed to listen: %s\n", strerror(errno));
return -1;
}
return sfd;
}
//函数作用:等待并接受客户端连接,返回新的连接套接字文件描述符。
int accept_client_connect(int sockfd, struct sockaddr_in *cli_addr) {
int cfd;
socklen_t len = sizeof(struct sockaddr_in);
//4.与客户端进行三次握手并建立连接 并返回新的套接字文件描述符
cfd = accept(sockfd, (struct sockaddr *) cli_addr, &len);
if (cfd == -1) {
DEBUG_INFO("[ERROR] Failed to accept: %s\n", strerror(errno));
return -1;
}
return cfd;
}
//显示连接客服端的地址信息
void show_tcp_network_address(struct sockaddr_in *sockaddr) {
//打印IP地址和端口信息
printf("ip : %s port : %d\n", inet_ntoa(sockaddr->sin_addr), ntohs(sockaddr->sin_port));
}
void *client_thread(void *arg) {
char buf[1024] = {0};
int rws = (int) arg;
//初始化buff,将buf设置为零
bzero(buf, sizeof(buf));
//接收客户端的数据
if (0 > recv(rws, buf, sizeof(buf) - 1, 0)) {
DEBUG_INFO("[ERROR] Failed to recv: %s\n", strerror(errno));
goto end;
}
request_t request = {
.type = 0,
.parm = NULL, //这里需要开辟空间存放http数据.
};
if (analyse_http_request(buf, &request) < 0) {
DEBUG_INFO("[ERROR] Failed to analyse_http_request: %s\n", strerror(errno));
goto end;
}
switch (request.type) {
case A_FILE:
send_file(rws, request.parm);
break;
case A_SNAPSHOT:
send_snapshot(rws);
break;
case A_STREAM:
send_stream(rws);
break;
}
end:
close(rws);
if (NULL != request.parm) {
free(request.parm);
}
return NULL;
}
int analyse_http_request(const char *buf, request_t *req) {
char *url = strstr(buf, "GET /");
if (NULL == url) {
printf("Http request error!\n");
return -1;
}
url += strlen("GET /");
char arr[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ._-1234567890";
int len = MIN(MAX(strspn(url, arr), 0), 100);
req->parm = (char *) malloc(len + 1);
memset(req->parm, 0, len + 1);
memcpy(req->parm, url, len);
printf("req->parm : %s.\n", req->parm);
req->type = A_FILE;
return 0;
}
void send_stream(int sockfd) {
int length;
char buf[BUFFER_SIZE];
printf("send_stream(%d)\n", sockfd);
sprintf(buf, "HTTP/1.1 200 OK\r\n" \
STD_HEADER \
"Content-Type: multipart/x-mixed-replace;boundary=" BOUNDARY "\r\n" \
"\r\n" \
"--" BOUNDARY "\r\n");
if (send(sockfd, buf, strlen(buf), 0) < 0) {
perror("send_stream() fail to send http head");
return;
}
char *frame = NULL;
while (global.capture) //how to stop?
{
pthread_mutex_lock(&global.update_lock);
pthread_cond_wait(&global.update_cond, &global.update_lock);
length = global.length;
frame = (char *) malloc(global.length);
memcpy(frame, global.start, global.length);
pthread_mutex_unlock(&global.update_lock);
/* print the individual mimetype and the length
* sending the content-length fixes random stream disruption observed
* with firefox
*/
sprintf(buf, "Content-Type: image/jpeg\r\n" \
"Content-Length: %d\r\n" \
"\r\n", length);
if (0 > send(sockfd, buf, strlen(buf), 0)) {
perror("send_stream() Fail to send http header");
break;
}
if (0 > send(sockfd, frame, length, 0)) {
perror("send_stream() Fail to send camera frame");
break;
}
sprintf(buf, "\r\n--" BOUNDARY "\r\n");
if (0 > send(sockfd, buf, strlen(buf), 0)) {
break;
}
free(frame);
usleep(100);
}
free(frame);
return;
}
void send_file(int sockfd, char *file_path) {
int n, fd;
char buf[1024] = {0};
char *extension, *mimetype = NULL;
if (file_path == NULL || strlen(file_path) == 0)
file_path = "index.html";
/* find file-extension */
if (NULL == (extension = strstr(file_path, "."))) {
// send_error(fd, 400, "No file extension found");
return;
}
/* determine mime-type */
for (int i = 0; i < sizeof(mimetypes) / sizeof(mimetypes[0]); ++i) {
if (0 == strcmp(mimetypes[i].dot_extension, extension)) {
mimetype = (char *) mimetypes[i].mimetype;
break;
}
}
if (NULL == mimetype) {
return;
}
//打开文件
sprintf(buf, "%s/%s", WEB_DIR, file_path);
if ((fd = open(buf, O_RDONLY)) < 0) {
fprintf(stderr, "Fail to open %s : %s.\n", buf, strerror(errno));
//send error to webbrowser
return;
}
// 添加http头.
memset(buf, 0, sizeof(buf));
sprintf(buf, "HTTP/1.0 200 OK\r\n" \
"Content-type: %s\r\n" \
STD_HEADER \
"\r\n", mimetype); //注意这里会多出个空行.
// 发送http头信息和网页文件数据.
n = strlen(buf);
//发送http头信息和网页文件数据.
do {
if (0 > send(sockfd, buf, n, 0)) {
perror("send_file() Fail to send file contain");
}
} while (n = read(fd, buf, sizeof(buf)));
return;
}
void send_snapshot(int sockfd) {
int length;
char *frame;
char buf[BUFFER_SIZE];
printf("send_snapshot(%d)\n", sockfd);
pthread_mutex_lock(&global.update_lock);
pthread_cond_wait(&global.update_cond, &global.update_lock);
//获得视频数据
length = global.length;
frame = (char *) malloc(global.length);
memcpy(frame, global.start, global.length);
pthread_mutex_unlock(&global.update_lock);
//添加http头
memset(buf, 0, sizeof(buf));
sprintf(buf, "HTTP/1.0 200 OK\r\n" \
"Content-type: image/jpeg\r\n" \
STD_HEADER \
"\r\n");
//发送http头
if (0 > send(sockfd, buf, strlen(buf), 0)) {
printf("send_snapshot() send http head failure\n");
free(frame);
return;
}
//发送视频数据
if (0 > send(sockfd, frame, length, 0)) {
printf("send_snapshot() send frame failure\n");
}
free(frame);
return;
}
main.c
#include
#include
#include
#include
#include
#include
#include "head/debug.h"
#include "head/camera.h"
#include "head/tcp_server.h"
#include "head/global.h"
/*
@Author: wjhu8
@Time: 24-10-29 下午8:57
@name: main.c
*/
int main(void) {
#if 0
int cmd_fd = init_camera("/dev/video0");
if (-1 == cmd_fd) {
perror("init_camera failure!\n");
return -1;
}
if (-1 == init_mmap(cmd_fd)) {
perror("init_mmap failure!\n");
return -1;
}
start_camera(cmd_fd);
//9.监测fd的读事件,并在可读时读取数据
struct timeval tv;
tv.tv_sec = 20;
tv.tv_usec = 0;
fd_set fds;
FD_ZERO(&fds);
FD_SET(cmd_fd, &fds);
int r = select(cmd_fd + 1, &fds, NULL, NULL, &tv);
if (0 == r) {
printf("no frame for read!\n");
return 0;
} else {
if (FD_ISSET(cmd_fd, &fds)) {
if (-1 == read_camera(cmd_fd)) {
printf("read_camera() failure!\n");
return -1;
}
}
}
#endif
int sfd, cfd, ret;
pthread_t tid;
struct sockaddr_in addr;
//1.创建 tcp server socket
sfd = create_tcp_server_socket("192.168.197.15", "8080");
while (1) {
//2.用于与客户端建立连接
cfd = accept_client_connect(sfd, &addr);
//3.显示连接客服端的地址信息
show_tcp_network_address(&addr);
//4.子进程执行任务
int ret = pthread_create(&tid, NULL, client_thread, (void *) cfd);
if (0 != ret) {
DEBUG_INFO("[ERROR] Failed to pthread_create: %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
pthread_detach(tid);
printf("loop again\n");
}
return 0;
}
【图片】
【图片】