一、公有头文件
unp.h
#include <sys/types.h> /* basic system data types */
#include <sys/socket.h> /* basic socket definitions */
#include <sys/time.h> /* timeval{} for select() */
#include <time.h> /* timespec{} for pselect() */
#include <netinet/in.h> /* sockaddr_in{} and other Internet defns */
#include <arpa/inet.h> /* inet(3) functions */
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define SA struct sockaddr
#define LISTENQ 1024
#define MAXLINE 4096 /* max text line length */
#define BUFFSIZE 8192 /* buffer size for reads and writes */
#define SERV_PORT 9877
int Socket(int, int, int);
void Bind(int, const SA *, socklen_t);
void Listen(int, int);
int Accept(int, SA *, socklen_t *);
pid_t Fork(void);
void Connect(int, const SA *, socklen_t);
void Close(int);
void Writen(int fd, void *ptr, size_t nbytes);
void str_echo(int sockfd);
const char* Inet_ntop(int family, const void *addrptr, char *strptr, size_t len);
void Inet_pton(int family, const char *strptr, void *addrptr);
void Fclose(FILE *);
FILE *Fdopen(int, const char *);
char *Fgets(char *, int, FILE *);
FILE *Fopen(const char *, const char *);
void Fputs(const char *, FILE *);
ssize_t Readline(int, void *, size_t);
void str_echo(int);
void str_cli(FILE *, int);
实现 wrap.c
#include "unp.h"
int
Socket(int family,int type,int protocol){
int n;
if((n=socket(family,type,protocol))< 0){
printf("socket error");
return n;
}
return n;
}
void
Bind(int fd, const struct sockaddr *sa, socklen_t salen){
if (bind(fd, sa, salen) < 0){
printf("bind error");
}
}
void
Listen(int socketfd,int backlog){
if(listen(socketfd,backlog) <0){
printf("listen error");
}
}
int
Accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen){
int n;
if((n=accept(sockfd,addr,addrlen))<0){
printf("accept error");
}
return n;
}
pid_t
Fork(void)
{
pid_t pid;
if ( (pid = fork()) == -1)
printf("fork error");
return pid;
}
void
Connect(int sockfd,const struct sockaddr *addr,socklen_t addrlen){
if(connect(sockfd,addr,addrlen)<0){
printf("connect error");
}
}
void
Close(int sockfd){
if(close(sockfd) <0)
printf("close error");
}
const char *
Inet_ntop(int family, const void *addrptr, char *strptr, size_t len)
{
const char *ptr;
if (strptr == NULL) /* check for old code */
printf("NULL 3rd argument to inet_ntop");
if ( (ptr = inet_ntop(family, addrptr, strptr, len)) == NULL)
printf("inet_ntop error"); /* sets errno */
return(ptr);
}
void
Inet_pton(int family, const char *strptr, void *addrptr)
{
int n;
if ( (n = inet_pton(family, strptr, addrptr)) < 0)
printf("inet_pton error for %s", strptr); /* errno set */
else if (n == 0)
printf("inet_pton error for %s", strptr); /* errno not set */
/* nothing to return */
}
void
Fclose(FILE *fp)
{
if (fclose(fp) != 0)
printf("fclose error");
}
FILE *
Fdopen(int fd, const char *type)
{
FILE *fp;
if ( (fp = fdopen(fd, type)) == NULL)
printf("fdopen error");
return(fp);
}
char *
Fgets(char *ptr, int n, FILE *stream)
{
char *rptr;
if ( (rptr = fgets(ptr, n, stream)) == NULL && ferror(stream))
printf("fgets error");
return (rptr);
}
FILE *
Fopen(const char *filename, const char *mode)
{
FILE *fp;
if ( (fp = fopen(filename, mode)) == NULL)
printf("fopen error");
return(fp);
}
void
Fputs(const char *ptr, FILE *stream)
{
if (fputs(ptr, stream) == EOF)
printf("fputs error");
}
ssize_t /* Write "n" bytes to a descriptor. */
writen(int fd, const void *vptr, size_t n)
{
size_t nleft;
ssize_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; /* and call write() again */
else
return(-1); /* error */
}
nleft -= nwritten;
ptr += nwritten;
}
return(n);
}
/* end writen */
void
Writen(int fd, void *ptr, size_t nbytes)
{
if (writen(fd, ptr, nbytes) != nbytes)
printf("writen error");
}
二、服务端
BaseSocketServer.c
#include "../unp.h"
int
main(int argc, char *args[])
{
int listenfd, connfd;
pid_t childpid;
socklen_t clilen;
struct sockaddr_in cliaddr, servaddr;
int port = atoi(args[1]);
//创建socket
listenfd = Socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(port);
//绑定端口
Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));
//监听端口
Listen(listenfd, LISTENQ);
for ( ; ; ) {
clilen = sizeof(cliaddr);
//接受客户端的连接
connfd = Accept(listenfd, (SA *) &cliaddr, &clilen);
printf("accept ip:%s,port:%d\n",inet_ntoa(cliaddr.sin_addr),htons(cliaddr.sin_port));
//fork子进程,处理该客户端交互
if ( (childpid = Fork()) == 0) { /* child process */
Close(listenfd); /* close listening socket */
str_echo(connfd); /* process the request */
exit(0);
}
Close(connfd); /* parent closes connected socket */
}
}
str_echo的实现
#include "../unp.h"
void
str_echo(int sockfd)
{
ssize_t n;
char buf[MAXLINE];
again:
while ( (n = read(sockfd, buf, MAXLINE)) > 0)
Writen(sockfd, buf, n);
if (n < 0 && errno == EINTR)
goto again;
else if (n < 0)
printf("str_echo: read error");
}
三、客户端
BaseSocketClient.c
#include "../unp.h"
int
main(int argc, char *args[])
{
int sockfd;
struct sockaddr_in servaddr;
if (argc <3)
printf("argc error\n");
int port = atoi(args[2]);
//创建socket
sockfd = Socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(port);
Inet_pton(AF_INET, args[1], &servaddr.sin_addr);
//连接服务端
Connect(sockfd, (SA *) &servaddr, sizeof(servaddr));
str_cli(stdin, sockfd); /* do it all */
exit(0);
}
str_cli的实现
#include "../unp.h"
void
str_cli(FILE *fp, int sockfd)
{
char sendline[MAXLINE], recvline[MAXLINE];
while (Fgets(sendline, MAXLINE, fp) != NULL) {
Writen(sockfd, sendline, strlen(sendline));
if (Readline(sockfd, recvline, MAXLINE) == 0)
printf("str_cli: server terminated prematurely");
Fputs(recvline, stdout);
}
}
Readline的实现
/* include readline */
#include "../unp.h"
static int read_cnt;
static char *read_ptr;
static char read_buf[MAXLINE];
static ssize_t
my_read(int fd, char *ptr)
{
if (read_cnt <= 0) {
again:
if ( (read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0) {
if (errno == EINTR)
goto again;
return(-1);
} else if (read_cnt == 0)
return(0);
read_ptr = read_buf;
}
read_cnt--;
*ptr = *read_ptr++;
return(1);
}
ssize_t
readline(int fd, void *vptr, size_t maxlen)
{
ssize_t n, rc;
char c, *ptr;
ptr = vptr;
for (n = 1; n < maxlen; n++) {
if ( (rc = my_read(fd, &c)) == 1) {
*ptr++ = c;
if (c == '\n')
break; /* newline is stored, like fgets() */
} else if (rc == 0) {
*ptr = 0;
return(n - 1); /* EOF, n - 1 bytes were read */
} else
return(-1); /* error, errno set by read() */
}
*ptr = 0; /* null terminate like fgets() */
return(n);
}
ssize_t
readlinebuf(void **vptrptr)
{
if (read_cnt)
*vptrptr = read_ptr;
return(read_cnt);
}
/* end readline */
ssize_t
Readline(int fd, void *ptr, size_t maxlen)
{
ssize_t n;
if ( (n = readline(fd, ptr, maxlen)) < 0)
printf("readline error");
return(n);
}
四、gcc编译服务端
gcc BaseSocketServer.c str_echo.c ../wrap.c -o server
- ./server 8899
绑定并且监听8899端口
五、gcc编译客户端
gcc BaseSocketClient.c readline.c str_cli.c ../wrap.c -o client
- ./client xxxx.xxxx.xxx 8899
客户端需要连接的ip地址以及端口数据,8899就是服务端监听的端口
六、使用CMake编译
如果我们直接使用gcc对程序进行编译的话,源文件少的话,还能接受,如果很多的话,就头大了!就采用CMake跨平台的交叉编译功能,来简化我们的编译。Cmake官方文档
6-1、服务端CMakeList.txt
cmake_minimum_required(VERSION 3.26)
PROJECT(BASIC)
INCLUDE_DIRECTORIES(../unp.h)
SET(SRC_LIST BaseSocketServer.c str_echo.c ../wrap.c)
ADD_EXECUTABLE(server ${SRC_LIST})
- cmake_minimum_required
设置CMake最小版本 - PROJECT
设置项目名称 - INCLUDE_DIRECTORIES
设置头文件 - SET
设置变量值 - ADD_EXECUTABLE
用于使用指定的源文件向项目(project)添加可执行文件
6-2、客户端CMakeLists.txt
cmake_minimum_required(VERSION 3.26)
PROJECT(BASIC_CLIENT)
INCLUDE_DIRECTORIES(../unp.h)
SET(SRC_LIST BaseSocketClient.c readline.c str_cli.c ../wrap.c)
ADD_EXECUTABLE(client ${SRC_LIST})
6-3、在CMakeLists.txt所在的目录下执行cmake指令
- cmake
cmake指令必须指定编译的路径 cmake .代表当前目录下 - 多余文件
使用该方式,会在源文件夹下产生很多多余的文件,影响整体项目的可读性,为了解决这样的问题,我们可以在这个源文件夹下建立一个build文件,专门用来保存编译后的文件
- cd build/
进入build文件夹 - cmake …
因为CMakeLists.txt文件在当前文件夹的上级目录下,所以使用…指向上级目录 - cmake生成的文件
生成的是Makefile相关的文件 - make
使用make命令编译源码 - server
该文件就是编译后生成的可执行文件
七、服务端监听中断信号
signal.c
#include "../unp.h"
Sigfunc *
signal(int signo, Sigfunc *func)
{
struct sigaction act, oact;
act.sa_handler = func;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
if (signo == SIGALRM) {
#ifdef SA_INTERRUPT
act.sa_flags |= SA_INTERRUPT; /* SunOS 4.x */
#endif
} else {
#ifdef SA_RESTART
act.sa_flags |= SA_RESTART; /* SVR4, 44BSD */
#endif
}
if (sigaction(signo, &act, &oact) < 0)
return(SIG_ERR);
return(oact.sa_handler);
}
/* end signal */
Sigfunc *
Signal(int signo, Sigfunc *func) /* for our signal() function */
{
Sigfunc *sigfunc;
if ( (sigfunc = signal(signo, func)) == SIG_ERR)
printf("signal error");
return(sigfunc);
}
sigchldwait.c
#include "../unp.h"
void
sig_chld(int signo)
{
pid_t pid;
int stat;
while ( (pid = waitpid(-1, &stat, WNOHANG)) > 0)
printf("child %d terminated\n", pid);
return;
}
服务端调整
#include "../unp.h"
int
main(int argc, char *args[])
{
int listenfd, connfd;
pid_t childpid;
socklen_t clilen;
struct sockaddr_in cliaddr, servaddr;
void sig_chld(int);
int port = atoi(args[1]);
listenfd = Socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(port);
Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));
Listen(listenfd, LISTENQ);
//必须在fork第一个子进程之前完成,并且只做一次
Signal(SIGCHLD, sig_chld); /* must call waitpid() */
for ( ; ; ) {
clilen = sizeof(cliaddr);
if ( (connfd = accept(listenfd, (SA *) &cliaddr, &clilen)) < 0) {
if (errno == EINTR)
continue; /* back to for() */
else
printf("accept error");
}
printf("accept ip:%s,port:%d\n",inet_ntoa(cliaddr.sin_addr),htons(cliaddr.sin_port));
if ( (childpid = Fork()) == 0) { /* child process */
Close(listenfd); /* close listening socket */
str_echo(connfd); /* process the request */
exit(0);
}
Close(connfd); /* parent closes connected socket */
}
}