基础版
封装的socket.h
warp_socket.h
#ifndef _WRAP__
#define _WRAP__
#include <sys/socket.h>
int check(int ret, char *str);
int my_socket(int domain, int type, int protocol);
int default_my_socket_ipv4_tcp();
struct sockaddr_in* create_ipv4_for_localhost(struct sockaddr_in *srv_addr, uint16_t port);
int my_bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
int my_listen(int sockfd, int backlog);
int my_accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
int my_connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
ssize_t my_read(int fd, void *buf, size_t count);
ssize_t my_write(int fd, const void *buf, size_t count);
int my_setsockopt(int sockfd);
#endif
warp_socket.c
#include <stdio.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <unistd.h>
#include <ctype.h>
#include <arpa/inet.h>
#include <errno.h>
#include <strings.h>
#define SOCKET_ERROR "socket error"
#define CONNECT_ERROR "connect error"
#define LISTEN_ERROR "listen error"
#define BIND_ERROR "bind error"
#define ACCEPT_ERROR "accept error"
#define PORT_ERROR "port error"
int check(int ret, char *str) {
if (ret == -1) {
perror(str);
exit(EXIT_FAILURE);
}
return ret;
}
int my_socket(int domain, int type, int protocol) {
return check(socket(domain, type, protocol), SOCKET_ERROR);
}
int default_my_socket_ipv4_tcp() {
return my_socket(AF_INET, SOCK_STREAM, 0);
}
struct sockaddr_in *create_ipv4_for_localhost(struct sockaddr_in *srv_addr, uint16_t port) {
//将地址结构清零
// memset(&srv_addr,0, sizeof(srv_addr));
bzero(srv_addr, sizeof(srv_addr));
srv_addr->sin_family = AF_INET;
srv_addr->sin_port = htons(port);
srv_addr->sin_addr.s_addr = htonl(INADDR_ANY);
return srv_addr;
}
int my_bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen) {
return check(bind(sockfd, addr, addrlen), BIND_ERROR);
}
int my_listen(int sockfd, int backlog) {
return check(listen(sockfd, backlog), LISTEN_ERROR);
}
int my_accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen) {
int i;
again :
if ((i = accept(sockfd, addr, addrlen)) < 0) {
//因为accept是个慢系统调用,发送信号后中断后,重试.
/** EINTR:如果进程在一个慢系统调用(slow system call)中阻塞时,
当捕获到某个信号且相应信号处理函数返回时,
这个系统调用被中断,调用返回错误,
设置errno为EINTR
*/
/**
* ECONNABORTED
* 该错误被描述为“software caused connection abort”,即“软件引起的连接中止”。
* 原因在于当服务和客户进程在完成用于 TCP 连接的“三次握手”后,
* 客户 TCP 却发送了一个 RST (复位)分节,在服务进程看来,就在该连接已由 TCP 排队,
* 等着服务进程调用 accept 的时候 RST 却到达了。POSIX 规定此时的 errno 值必须 ECONNABORTED。
* 源自 Berkeley 的实现完全在内核中处理中止的连接,
* 服务进程将永远不知道该中止的发生。服务器进程一般可以忽略该错误,直接再次调用accept。
*/
if ((errno == ECONNABORTED) || (errno == EINTR)) {
//重试等待连接
goto again;
} else {
return check(i, ACCEPT_ERROR);
}
}
}
int my_connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen) {
return check(connect(sockfd, addr, addrlen), CONNECT_ERROR);
}
ssize_t my_read(int fd, void *buf, size_t count) {
ssize_t i;
again :
if ((i = read(fd, buf, count)) < 0) {
if (errno == EINTR) {
goto again;
} else {
return -1;
}
}
return i;
}
/**
* 重复读取count个数数据
*
* @param fd
* @param buf
* @param count 读取指定个数,应该<=buf空间
* @return
*/
ssize_t my_readn(int fd, void *buf, size_t count) {
ssize_t i;
//记录剩余尚未读取个数
ssize_t nleft = count;
char *ptr = buf;
while (nleft > 0) {
if ((i = read(fd, buf, nleft)) < 0) {
if (errno == EINTR) {
i = 0;
} else {
return -1;
}
} else if (i == 0) {
break;
}
//说明读取成功
//重新计算应读个数,以及指针偏移
nleft -= i;
ptr += i;
}
//返回已读个数
return count - nleft;
}
ssize_t my_writen(int fd, void *buf, size_t count) {
ssize_t i;
ssize_t nleft = count;
char *ptr = buf;
while (nleft > 0) {
if ((i = write(fd, buf, nleft)) <= 0) {
if (i < 0 && errno == EINTR) {
i = 0;
} else {
return -1;
}
}
nleft -= i;
ptr += i;
}
//返回已写个数
return count - nleft;
}
ssize_t my_write(int fd, const void *buf, size_t count) {
ssize_t i;
again :
if ((i = write(fd, buf, count)) < 0) {
if (errno == EINTR) {
goto again;
} else {
return -1;
}
}
return i;
}
/**
* 设置端口复用,也就是可以重复利用同一个端口运行服务器
*/
int my_setsockopt(int sockfd) {
//是否启用,0不启用
int opt = 1;
return check(setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (void *) &opt, sizeof(opt)), PORT_ERROR);
}
客户端
#include <stdio.h>
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>
#include "warp_socket.h"
#define PORT 8080
int main() {
char buf[BUFSIZ];
struct sockaddr_in serv_addr;
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(PORT);
// inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr.s_addr);
inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr);
int cfd = default_my_socket_ipv4_tcp();
int ret = my_connect(cfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr));
while (1) {
my_write(cfd, "hello\n", 6);
sleep(1);
int size = my_read(cfd, buf, sizeof(buf));
my_write(STDOUT_FILENO, buf, size);
}
close(cfd);
return 0;
}
服务端
#include <stdio.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <unistd.h>
#include <ctype.h>
#include <arpa/inet.h>
#include "warp_socket.h"
/**
* 一个文件描述符指向一个套接字,该套接字内部由内核借助2个缓冲区实现
*
* 网络字节序
* 小端存储法(pc本地存储):高位存高地址,低位存低地址.
* 大端存储法(网络存储):同上相反.
* h表示host,n表示network,l表示32位整数,s表示16位短整数
* htonl:本地 => 网络(IP) 只接收32位整数,所以需要先将点分十进制形式(192.XX.XX.XX)的ip地址转成整数, INADDR_ANY获取系统有效IP地址,32整型
* htons:本地 => 网络(port)
* ntohl 网络 => 本地(IP)
* ntohs 网络 => 本地(port)
*
* IP地址转换
* int inet_pton(int af, const char *src, void *dst); 本地(string) =>网络
* af : AF_INET / AF_INET6 ipv4 /ipv6
* src : IP地址(点分10进制形式)
* dst : 传出参数,网络字节序的ip地址
*
* const char *inet_ntop(int af, const void *src,char *dst, socklen_t size); 网络 => 本地(string)
*/
#define PORT 8080
int main() {
int lfd, cfd;
int ret;
char buf[BUFSIZ], cli_Ip[1024];
struct sockaddr_in serv_addr, clit_addr;
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(PORT);
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
/**
* int socket(int domain, int type, int protocol);
* domain : ip地址协议
* type : 数据协议 流式协议,报式协议
* protocol : 选用协议的代表协议. 流式就是tcp,报式就是udp
*/
lfd = default_my_socket_ipv4_tcp();
/**
* int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
* struct sockaddr_in {
sa_family_t sin_family; IP地址协议
in_port_t sin_port; 端口(网络字节序)
struct in_addr sin_addr; ip(网络字节序)
}
sockaddr_in主要弥补sockaddr的缺陷(目标地址和端口信息混在一起),占用字节大小都一样.
*/
my_bind(lfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr));
//最多允许连接数
my_listen(lfd, 128);
//监听并处理连接
socklen_t client_addr_len = sizeof(clit_addr);
cfd = my_accept(lfd, (struct sockaddr *) &clit_addr, &client_addr_len);
printf("client ip: %s , port: %d \n", inet_ntop(AF_INET, &clit_addr.sin_addr.s_addr, cli_Ip, sizeof(cli_Ip)),
ntohs(clit_addr.sin_port)
);
while (1) {
ret = my_read(cfd, buf, sizeof(buf));
my_write(STDOUT_FILENO, buf, ret);
for (int i = 0; i < ret; ++i) {
buf[i] = toupper(buf[i]);
}
my_write(cfd, buf, ret);
}
close(cfd);
close(lfd);
return 0;
}
多进程版本
#include <stdio.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <unistd.h>
#include <ctype.h>
#include <arpa/inet.h>
#include <signal.h>
#include <wait.h>
#include "warp_socket.h"
/**
* 多进程
* @return
*/
#define PORT 8080
void killP() {
while (waitpid(0, NULL, WNOHANG) > 0);
return;
}
int main() {
int lfd, cfd;
int ret;
char buf[BUFSIZ];
pid_t pid;
struct sockaddr_in srv_addr, cli_addr;
create_ipv4_for_localhost(&srv_addr, PORT);
lfd = default_my_socket_ipv4_tcp();
my_bind(lfd, (struct sockaddr *) &srv_addr, sizeof(srv_addr));
my_listen(lfd, 128);
socklen_t cli_addr_len = sizeof(cli_addr);
while (1) {
cfd = my_accept(lfd, (struct sockaddr *) &cli_addr, &cli_addr_len);
pid = fork();
if (pid < 0) {
exit(EXIT_FAILURE);
} else if (pid == 0) {
//关闭主进程accept描述符
close(lfd);
break;
} else {
//关闭子进程描述符
close(cfd);
//回收子进程
struct sigaction act;
act.sa_handler = killP;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
ret = sigaction(SIGCHLD, &act, NULL);
if (ret != 0) {
perror("error");
exit(EXIT_FAILURE);
}
continue;
}
}
if (pid == 0) {
while (1) {
ret = my_read(cfd, buf, sizeof(buf));
printf("%d \n", ret);
//读到文件末尾或者客户端主动断开,则直接返回
if (ret <= 0) {
close(cfd);
perror("error");
exit(EXIT_FAILURE);
}
my_write(STDOUT_FILENO, buf, ret);
for (int i = 0; i < ret; i++) {
buf[i] = toupper(buf[i]);
}
my_write(cfd, buf, ret);
}
}
return 0;
}
多线程版本
#include <stdio.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <unistd.h>
#include <ctype.h>
#include <arpa/inet.h>
#include <signal.h>
#include <wait.h>
#include <pthread.h>
#include "warp_socket.h"
/**
* 多线程
* @return
*/
#define PORT 8080
struct s_info {
struct sockaddr_in cliaddr;
int fd;
};
void *work_fun(void *s_info) {
struct s_info *s = (struct s_info *) s_info;
int ret;
char buf[BUFSIZ];
char str[INET_ADDRSTRLEN];
while (1) {
ret = my_read(s->fd, buf, BUFSIZ);
printf("%d \n", ret);
//读到文件末尾或者客户端主动断开,则直接返回
if (ret <= 0) {
break;
}
printf("received from %s at PORT %d \n", inet_ntop(AF_INET, &(*s).cliaddr.sin_addr, str, INET_ADDRSTRLEN),
ntohs((*s).cliaddr.sin_port));
printf("received from %s at PORT %d \n", inet_ntop(AF_INET, &(s->cliaddr.sin_addr), str, INET_ADDRSTRLEN),
ntohs(s->cliaddr.sin_port));
my_write(STDOUT_FILENO, buf, ret);
for (int i = 0; i < ret; i++) {
buf[i] = toupper(buf[i]);
}
my_write(s->fd, buf, ret);
}
close(s->fd);
return (void *) 0;
}
int main() {
int lfd, cfd;
int i = 0;
struct sockaddr_in srv_addr, cli_addr;
socklen_t cli_addr_len = sizeof(cli_addr);
struct s_info t[256];
create_ipv4_for_localhost(&srv_addr, PORT);
lfd = default_my_socket_ipv4_tcp();
my_bind(lfd, (struct sockaddr *) &srv_addr, sizeof(srv_addr));
my_listen(lfd, 128);
pthread_t tid;
printf("wait for Accept... ");
while (1) {
cfd = my_accept(lfd, (struct sockaddr *) &cli_addr, &cli_addr_len);
t[i].cliaddr = cli_addr;
t[i].fd = cfd;
pthread_create(&tid, NULL, work_fun, (void *) &t[i]);
//线程分离,防止僵尸线程产生.
pthread_detach(tid);
i = (++i) % 256;
}
return 0;
}
UDP实现
client.c
#include <stdio.h>
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <strings.h>
#define PORT 8080
int main() {
char buf[BUFSIZ];
int cfd;
int n;
struct sockaddr_in serv_addr;
socklen_t serv_addr_len = sizeof(serv_addr);
bzero(&serv_addr, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(PORT);
inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr);
cfd = socket(AF_INET, SOCK_DGRAM, 0);
while (1) {
//相当于connect+write
n = sendto(cfd, "hello\n", 6, 0, (struct sockaddr *) &serv_addr, serv_addr_len);
if (n == -1) {
perror("error");
}
n = recvfrom(cfd, buf, BUFSIZ, 0, NULL, 0); //不需要服务器的信息
write(STDOUT_FILENO, buf, n);
if (n == -1) {
perror("error");
}
sleep(1);
}
close(cfd);
return 0;
}
server.c
#include <stdio.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <unistd.h>
#include <ctype.h>
#include <arpa/inet.h>
#include <strings.h>
/**
* upd 通信
* @return
*/
#define PORT 8080
int main() {
int lfd;
int n;
char buf[BUFSIZ], cli_Ip[1024];
struct sockaddr_in serv_addr, clit_addr;
socklen_t client_addr_len = sizeof(clit_addr);
lfd=socket(AF_INET, SOCK_DGRAM, 0);
bzero(&serv_addr, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(PORT);
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
bind(lfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr));
printf("acepting...");
while (1){
//相当于accpet+write
n = recvfrom(lfd, buf,BUFSIZ,0,(struct sockaddr *)&clit_addr,&client_addr_len);
if (n==-1){
perror("error");
}
write(STDOUT_FILENO, buf, n);
printf("client ip: %s , port: %d \n", inet_ntop(AF_INET, &clit_addr.sin_addr.s_addr, cli_Ip, sizeof(cli_Ip)),
ntohs(clit_addr.sin_port));
for (int i = 0; i < n; ++i) {
buf[i] = toupper(buf[i]);
}
n=sendto(lfd, buf, n,0,(struct sockaddr *)&clit_addr, sizeof(clit_addr));
if (n==-1){
perror("error");
}
}
close(lfd);
return 0;
}