最近在看《UNIX编程卷1:套接字联网API》,一般都是ssh到本地的linux虚拟机里启动服务器端,然后再另一个ssh窗口启动客户端来测试。今天想了想可用nodejs在windows下实现回射程序的客户端:
echoCli.js:
var net = require('net'),
readline = require('readline');
var serverInfo = {
"host": "192.168.147.128",
"port": 9983
};
var client = net.connect(serverInfo);
client.on('data', function(data) {
console.log(data.toString());
});
client.on('end', function() {
console.log('client disconnected');
});
var rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
rl.on('line', function (str) {
client.write(str);
});
echoServer.c:
#include <stdlib.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include "../common.h"
#line 18 "tcpechoserv01.c"
void str_echo(int fd);
void sig_chld(int signo);
int main(void)
{
// 注册信号处理函数
signal(SIGCHLD, sig_chld);
int sockfd, connfd;
struct sockaddr_in servaddr, cliaddr, tmpaddr;
char addr_str[INET_ADDRSTRLEN + 1];
socklen_t cliaddrlen, tmplen;
pid_t pid;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd < 0) {
perror("socket error");
return 1;
}
bzero(&cliaddr, sizeof(cliaddr));
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(9983);
if (bind(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) != 0) {
perror("bind error");
return 1;
}
if (listen(sockfd, 1024) != 0) {
perror("listen error");
return 1;
}
// 输出服务器绑定的ip和端口号,tmplen初始化是必须的,不然会出错
tmplen = sizeof(tmpaddr);
if (getsockname(sockfd, (struct sockaddr *)&tmpaddr, &tmplen) != 0) {
perror("getsockname error");
return 1;
}
inet_ntop(AF_INET, (struct in_addr *)&tmpaddr.sin_addr, addr_str, sizeof(addr_str));
printf("listen IP: %s, PORT: %d\n", addr_str, ntohs(tmpaddr.sin_port));
for(;;) {
connfd = accept(sockfd, (struct sockaddr *)&cliaddr, &cliaddrlen);
if (connfd < 0) {
// 处理由信号引起的系统调用中断
if (errno == EINTR) {
continue;
}
perror("accept error");
exit(1);
}
// 子进程
if ((pid = fork()) == 0) {
// 打印客户端IP和端口
printf("child-process: client IP: %s, PORT: %d\n", inet_ntop(AF_INET, (struct in_addr *)&cliaddr.sin_addr, addr_str, sizeof(addr_str)), ntohs(cliaddr.sin_port));
// 关闭引用
if (close(sockfd) != 0){
perror("child-process: close sockfd error");
exit(1);
}
// 回射
str_echo(connfd);
// 子进程结束会关闭所有打开的描述符
exit(0);
}
// 父进程关闭connfd,减少引用数
if (close(connfd) != 0){
perror("parent-process: close connfd error");
exit(1);
}
}
}
/**
* 回射字符串函数
*/
void str_echo(int fd)
{
ssize_t n;
char buff[MAXLINE];
printf("str_echo: before read.\n");
again:
while((n = read(fd, buff, MAXLINE-1)) > 0) {
writen(fd, buff, n);
}
if (n < 0 && errno == EINTR) {
goto again;
} else if (n < 0) {
perror("str_echo: read error");
exit(1);
}
}
/**
* 信号:SIGCHLD处理函数
*/
void sig_chld(int signo)
{
pid_t pid;
int stat;
// wait会错过信号
//pid = wait(&stat);
// -1: 等待第一个终止的子进程
while((pid = waitpid(-1, &stat, WNOHANG)) > 0) {
// 在信号处理函数中调用IO操作是不合理的
printf("child %d terminated\n", pid);
}
return;
}
common.c:
#include "common.h"
// static 会把int默认值设置为0
static int read_cnt;
static char *read_ptr;
static char read_buff[MAXLINE- 1];
/**
* 每次读一个字符
*/
static size_t my_read(int fd, char *ptr)
{
if (read_cnt <= 0) {
again:
if ((read_cnt = read(fd, read_buff, sizeof(read_buff))) < 0) {
if (errno == EINTR) {
goto again;
}
return (-1);
} else if (read_cnt == 0) {
return (0);
}
read_ptr = read_buff;
}
read_cnt --;
*ptr = *read_ptr++;
return (1);
}
/**
* 指示buff的函数,void指针会自动转换
*/
size_t readlinebuf(void **vptrptr)
{
if (read_cnt) {
*vptrptr = read_ptr;
}
return (read_cnt);
}
/**
* 每次读n个字符
*/
size_t readn(int fd, void *vptr, size_t n)
{
size_t nleft;
size_t nread;
char *ptr;
ptr = vptr;
nleft = n;
while(nleft > 0) {
if ((nread = read(fd, ptr, nleft)) < 0) {
if (errno == EINTR) {
nread = 0;
} else {
return (-1);
}
} else if (nread == 0) {
break;
}
nleft -= nread;
ptr += nread;
}
return (n - nleft);
}
/**
* 每次写n个字符
*/
size_t writen(int fd, const void *vptr, size_t n)
{
size_t nleft;
size_t nwritten;
const char * ptr;
ptr = vptr;
nleft = n;
while(nleft > 0) {
if ((nwritten = write(fd, ptr, nleft)) <= 0) {
if (nwritten < 0 && errno == EINTR) {
nwritten = 0;
} else {
return (-1);
}
}
nleft -= nwritten;
ptr += nwritten;
}
return (n);
}
size_t readline(int fd, void *vptr, size_t maxlen)
{
size_t nleft, nread;
char *ptr, c;
ptr = vptr;
nleft = maxlen;
// 保留一字节的空字符位置
while(nleft > 1 && (nread = my_read(fd, &c)) != 0) {
if(nread == 1) {
*ptr++ = c;
nleft --;
if (c == '\n') {
break;
}
} else if (nread < 0) {
if (errno == EINTR) {
continue;
}
return (-1);
}
}
*ptr = 0;
return (maxlen - nleft);
}
/**
* 老的readline函数
*/
size_t readline_old(int fd, void *vptr, size_t maxlen)
{
size_t n, rc;
char c, *ptr;
ptr = vptr;
// n = 1,保留一个空字符
for (n = 1; n < maxlen; n++) {
again:
if((rc = read(fd, &c, 1)) == 1) {
*ptr++ = c;
if (c == '\n') {
break;
}
} else if (rc == 0) {
// 尾部放入空字符
*ptr = 0;
} else {
if (errno == EINTR) {
goto again;
}
return (-1);
}
}
*ptr = 0;
return (n);
}
common.h:
#include <unistd.h>
#include <errno.h>
#ifndef MAXLINE
#define MAXLINE 4096
#endif
size_t readn(int fd, void *buff, size_t n);
size_t writen(int fd, const void *buff, size_t n);
size_t readline(int fd, void *buff, size_t maxlen);
size_t readline_old(int fd, void *buff, size_t maxlen);
协议是沟通的桥梁。