1. 头文件
vi server.h
// 头文件
// 注册信号处理函数
int catch_signal(int sig, void (*handler) (int));
// 从socket读数据到char *buf
int read_in(int socket, char *buf, int len);
// 错误函数, 当exit_val=0只输出错误信息, 不退出程序. 其他值输出错误信息并退出程序
void error(char * msg, int exit_val);
// 创建监听socket
int open_listener_socket();
// 绑定socket到端口port
void bind_to_port(int socket, int port);
// 监听socket, 允许队列数queue.
void listen_to_socket(int socket, int queue);
// 开始等待客户端连接.
int accept_socket(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
// 向socket发送信息
int say(int socket, char *s);
// 信号处理函数
void handle_shutdown(int sig);
2. c文件
vi server.c
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <signal.h>
#include "server.h"
int catch_signal(int sig, void (*handler) (int)) {
struct sigaction action;
action.sa_handler = handler;
sigemptyset(&action.sa_mask);
// sigemptyset() return 0 on success and -1 on error.
action.sa_flags = 0;
return sigaction(sig, &action, NULL);
// sigaction() returns 0 on success and -1 on error.
}
// recv()注意:
// 1. The characters are not terminated with a \0 character.
// 2. When someone types text in telnet, the string always ends \r\n.
// 3. The recv() will return the number of characters, or –1 if there's an error, or
// 0 if the client has closed the connection.
// 4. You're not guaranteed to receive all the characters in a single call to recv().
// This reads all the characters until it reaches '\n'.
int read_in(int socket, char *buf, int len) {
char *s = buf;
int slen = len;
int c = recv(socket, s, slen, 0);
// Keep reading until there are no more characters or you reach '\n'.
while ( (c > 0) && (s[c-1] != '\n') ) {
s += c; slen -= c;
c = recv(socket, s, slen, 0);
}
if (c < 0)
// In case there's an error
return c;
else if (c == 0)
// Nothing read; send back an empty string
buf[0] = '\0';
else
// Replace the last character(here is '\n') with a '\0'.
s[c-1] = '\0';
return len-slen;
}
void error(char * msg, int exit_val) {
fprintf(stderr, "%s: %s\n", msg, strerror(errno));
// if exit_val == 0, not exit the program.
if (exit_val)
exit(exit_val);
}
int open_listener_socket() {
// Create an Internet streaming socket.
int s = socket(PF_INET, SOCK_STREAM, 0);
if (s == -1)
error("Can't open socket", 1);
return s;
}
void bind_to_port(int socket, int port) {
struct sockaddr_in name;
name.sin_family = PF_INET;
name.sin_port = (in_port_t) htons(port);
name.sin_addr.s_addr = htonl(INADDR_ANY);
// Yes, reuse the socket (so you can restart the server without problems).
int reuse = 1;
if (setsockopt(socket, SOL_SOCKET, SO_REUSEADDR, (char *) &reuse, sizeof(int)) == -1)
error("Can't set the reuse option on the socket", 1);
int c = bind(socket, (struct sockaddr *) &name, sizeof(name));
if(c == -1)
error("Can't bind to socket", 1);
}
void listen_to_socket(int socket, int queue) {
int l = listen(socket, queue);
if(l == -1)
error("Can't listen", 1);
}
// wait client connect to this server. return the secondary socketfd.
int accept_socket(int sockfd, struct sockaddr *addr, socklen_t *addrlen) {
int connect_d = accept(sockfd, addr, addrlen);
if (connect_d == -1)
error("Can't open secondary socket", 1);
return connect_d;
}
// Send a string to a client.
int say(int socket, char *s) {
int result = send(socket, s, strlen(s), 0);
if(result == -1)
// when error don't exit.
error("Error talking to the client", 0);
return result;
}
// This will store the main listener socket for the server.
int listener_d;
// If someone hits Ctrl-C when the server is running,
// this function will close the socket before the program ends.
void handle_shutdown(int sig) {
if(listener_d)
close(listener_d);
fprintf(stderr, "Signal:%i, Bye!\n", sig);
exit(0);
}
int main(int argc, char *argv[]) {
int port;
int queue;
char opt;
// get option argument
while ((opt = getopt(argc, argv, "hp:q:")) != EOF) {
switch(opt) {
case 'h':
fprintf(stdout, "%s -p $port_num -q $queue_num\n", argv[0]);
exit(1);
case 'p':
port = atoi(optarg);
break;
case 'q':
queue = atoi(optarg);
break;
default:
fprintf(stderr, "Unknown option: %s\n", optarg);
return 1;
}
}
// if argument count is not 5, exit main function.
if(argc != 5) {
fprintf(stdout, "%s -p $port_num -q $queue_num\n", argv[0]);
return 1;
}
// 注册SIGINT 到信号处理函数handle_shutdown.
if(catch_signal(SIGINT, handle_shutdown) == -1)
error("Can't set the interrupt handler", 1);
// 打开一个data stream socket.
listener_d = open_listener_socket();
// 绑定到端口port
bind_to_port(listener_d, port);
// 监听, 并指定queue长度
listen_to_socket(listener_d, queue);
// 准备接收客户端连接
struct sockaddr_storage client_addr;
unsigned int address_size = sizeof(client_addr);
puts("Waiting for connection");
char buf[255];
// accept_socket等待客户端连接.
while(1) {
// accept将创建the secondary socketfd.
int connect_d = accept_socket(listener_d, (struct sockaddr *) &client_addr, &address_size);
// 子进程处理客户端请求
if ( !fork() ) {
close(listener_d);
if (say(connect_d, "Internet Knock-Knock Protocol Server\r\nVersion 1.0\r\nKnock! Knock!\r\n> ") != -1) {
read_in(connect_d, buf, sizeof(buf));
}
if (strncasecmp("Who's there?", buf, 12))
say(connect_d, "You should say 'Who's there?'!");
else {
if (say(connect_d, "Oscar\r\n> ") != -1) {
read_in(connect_d, buf, sizeof(buf));
if (strncasecmp("Oscar who?", buf, 10))
say(connect_d, "You should say 'Oscar who?'!\r\n");
else
say(connect_d, "Oscar silly question, you get a silly answer\r\n");
}
}
// 子进程处理完请求后关闭the secondary socketfd. 并退出.
close(connect_d);
exit(0);
}
// 主进程不与客户端继续交互, 所以直接关闭the secondary socketfd. 继续while循环, 等待客户端连接.
close(connect_d);
}
return 0;
}
2. 编译, 执行.
[root@db-172-16-3-150 zzz]# gcc -O3 -Wall -Wextra -Werror -I. -g ./server.c -o server
[root@db-172-16-3-150 zzz]# ./server -h
./server -p $port_num -q $queue_num
[root@db-172-16-3-150 zzz]# ./server -p 30000 -q 10 // 因为本例用子进程处理客户端请求, 主进程及时的close(connect_d), 所以这个queue不需要太大. 0就可以了.
Waiting for connection
其他会话1 :
[root@db-172-16-3-150 zzz]# telnet 172.16.3.150 30000
Trying 172.16.3.150...
Connected to db-172-16-3-150.sky-mobi.com (172.16.3.150).
Escape character is '^]'.
Internet Knock-Knock Protocol Server
Version 1.0
Knock! Knock!
> hello
You should say 'Who's there?'!Connection closed by foreign host.
其他会话2 :
[root@db-172-16-3-150 zzz]# telnet 172.16.3.150 30000
Trying 172.16.3.150...
Connected to db-172-16-3-150.sky-mobi.com (172.16.3.150).
Escape character is '^]'.
Internet Knock-Knock Protocol Server
Version 1.0
Knock! Knock!
> Who's there?
Oscar
> Oscar who?
Oscar silly question, you get a silly answer
Connection closed by foreign host.
【注意】
1. NAME
listen - listen for connections on a socket
SYNOPSIS
#include <sys/socket.h>
int listen(int sockfd, int backlog);
NOTES
The behaviour of the backlog parameter on TCP sockets changed with Linux 2.2. Now it specifies the queue
length for completely established sockets waiting to be accepted, instead of the number of incomplete connec-
tion requests. The maximum length of the queue for incomplete sockets can be set using the tcp_max_syn_backlog
sysctl. When syncookies are enabled there is no logical maximum length and this sysctl setting is ignored.
See tcp(7) for more information.
时间: 2024-10-28 12:47:23