传输控制协议(英语:Transmission Control Protocol,缩写:TCP)是一种面向连接的、可靠的、基于字节流的传输层通信协议,由IETF的RFC 793定义。在简化的计算机网络OSI模型中,它完成第四层传输层所指定的功能。用户数据报协议(UDP)是同一层内另一个重要的传输协议。
往期推荐:
给俺的翠花女神发邮件告白,我学会了这些网络基础知识
史上最全的Linux常用命令汇总(超全面!超详细!)收藏这一篇就够了!
TCP编程的API
sockaddr地址结构
可以通过man 7 ip进行查看文档
struct sockaddr_in {
sa_family_t sin_family; /* address family: AF_INET */
in_port_t sin_port; /* port in network byte order */
struct in_addr sin_addr; /* internet address */
};
struct in_addr {
uint32_t s_addr; /* address in network byte order */
};
socket()函数
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain,int type,int protocol);
- domain参数
Name | Purpose | Man page |
---|---|---|
AF_UNIX, AF_LOCAL | Local communication | unix(7) |
AF_INET | IPv4 Internet protocols | ip(7) |
AF_INET6 | IPv6 Internet protocols | ipv6(7) |
AF_NETLINK | Kernel user interface device | netlink(7) |
AF_PACKET | Low level packet interface | packet(7) |
- type参数
参数名 | 作用 |
---|---|
SOCK_STREAM | 流式套接字,唯一对应TCP |
SOCK_DGRAM | 数据报套接字,唯一对应这UDP |
SOCK_RAW | 原始套接字 |
- protocol一般都是0,原始套接字时需要填充
- 成功时返回文件描述符,出错时返回为 - 1
bind()函数
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
- sockfd是返回socket函数的返回值
- addr:传入参数
(struct sockaddr *)&addr
- addrlen是
sizeof(addr)
地址的大小 - 成功返回0,失败返回-1
listen()函数
int listen(int sockfd, int backlog); //设置同时与服务器建立连接的上限数。(同时进行 3次握手的客户端数量)
- sockfd: socket 函数返回值
- backlog:上限数值。最大值 128.
- 返回值:
- 成功: 0
- 失败: -1 errno
accept()函数
阻塞等待客户端建立连接,成功的话,返回一个与客户端成功连接的 socket 文件描述符。
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
- sockfd: socket 函数返回值
- addr:传出参数。成功与服务器建立连接的那个客户端的地址结构( IP+port)
- socklen_t clit_addr_len = sizeof(addr);
- addrlen:传入传出。 &clit_addr_len
- 入: addr 的大小。 出:客户端 addr 实际大小。
- 返回值:
- 成功:能与客户端进行数据通信的 socket 对应的文件描述。
- 失败: -1 , errno
connect()函数
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); //使用现有的socket与服务器建立连接
- sockfd: socket 函数返回值
- struct sockaddr_in srv_addr; // 服务器地址结构
srv_addr.sin_family = AF_INET;
srv_addr.sin_port = 9527 跟服务器 bind 时设定的 port 完全一致。 - inet_pton(AF_INET, “服务器的 IP 地址”, &srv_adrr.sin_addr.s_addr);
- addr:传入参数。服务器的地址结构
- addrlen:服务器的地址结构的大小
- 返回值:
- 成功: 0
- 失败: -1 errno
如果不使用 bind 绑定客户端地址结构, 采用"隐式绑定"。
字节序相关函数
在计算机内部通常都采取小端法(高位存高地址,地位存低地址)存储字节序。而网络中存储字节序通常采取大端法(高位存低地址,地位存高地址)。
函数 | 作用 |
---|---|
uint32_t htonl(uint32_t hostlong); |
本地IP转换为网络IP |
uint16_t htons(uint16_t hostshort); |
本地端口转网络端口 |
uint32_t ntohl(uint32_t netlong); |
网络IP转本地IP |
uint16_t ntohs(uint16_t netshort); |
网络端口转本地端口 |
注意:h(host)代表主机,n(net)代表网络,l代表long整型,s代表short型
IP地址转换函数
通过htonl函数将本地IP转为网络IP需要先将点分十进制的IP(192.168.1.1本质是一个string)通过atoi函数转换为整数,再调用htonl函数来将这个整数转换为网络字节序。
- 本地IP转网络IP
#include <arpa/inet.h>
int inet_pton(int af, const char *src, void *dst);
参数 | 参数意义 |
---|---|
af: | AF_INET、 AF_INET6 |
src | 传入, IP 地址(点分十进制) |
dst | 传出,转换后的 网络字节序的 IP 地址。 |
返回值 | 成功: 1异常: 0, 说明 src 指向的不是一个有效的 ip 地址。 失败: -1 |
- 网络字节序转换为本地字节序
#include <arpa/inet.h>
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
参数 | 参数意义 |
---|---|
af | AF_INET、 AF_INET6 |
src | 网络字节序 IP 地址 |
dst | 本地字节序( string IP) |
size | dst 的大小。 |
返回值 | 成功: dst; 失败: NULL |
CS模型与BS模型对比
CS模型:client——server
BS模型:browser——server
对比项目 | C/S | B/S |
---|---|---|
优点: | 缓存大量数据、协议选择灵活 | 安全性、跨平台、开发工作量较小速度快 |
缺点: | 安全性、跨平台、开发工作量较大 | 不能缓存大量数据、严格遵守 http |
服务端的实现
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <sys/types.h>
#include <sys/socket.h>
#define SERV_PORT 5001
#define SERV_IP_ADDR "192.168.1.105"
#define QUIT_STR "quit"
int main(void)
{
int fd=-1;
struct sockaddr_in sin,clit_addr;
socklen_t clit_addr_len,client_IP[1024];
//一、初始化监听套接字
if((fd=socket(AF_INET,SOCK_STREAM,0))<0){
perror("socket");
exit(1);
}
/*二、填充结构体sockaddr_in的结构体变量*/
bzero(&sin,sizeof(sin));//对结构体变量进行清零
sin.sin_family=AF_INET;
sin.sin_port=htons(SERV_PORT);//本地字节序转网络字节序端口号
sin.sin_addr.s_addr=htonl(INADDR_ANY);
/* if(inet_pton(AF_INET,SERV_IP_ADDR,(void *)&sin.sin_addr)!=1)//把本地地址转为网络地址,如果不等于0说明转化失败
{
perror("inet_pton");
exit(1);
}*/
if(bind(fd,(struct sockaddr *)&sin,sizeof(sin))<0){
//绑定
perror("bind");
exit(1);
}
/*三、调用listen()把主动套接字变成被动套接字*/
if(listen(fd,128)<0){
perror("bind");
exit(1);
}
/*四、阻塞等待客户端连接请求*/
int newfd=-1,ret;
clit_addr_len=sizeof(clit_addr);
newfd=accept(fd,(struct sockaddr *)&clit_addr,&clit_addr_len);
if(newfd<0)
{
perror("accept");
exit(1);
}
printf("client ip:%s port: %d\n",
inet_ntop(AF_INET,&clit_addr.sin_addr.s_addr,client_IP,sizeof(client_IP))
,ntohs(clit_addr.sin_port));
char buf[BUFSIZ];
while(1){
bzero(buf,BUFSIZ);
ret=read(newfd,buf,sizeof(buf));
write(STDOUT_FILENO,buf,ret);
write(newfd,buf,ret);
}
close(newfd);
close(fd);
return 0;
}
客户端的实现
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#define SERV_PORT 5001
#define SERV_IP_ADDR "192.168.1.105"
#define BACKLOG 5
#define QUIT_STR "quit"
int main(void)
{
int fd=-1,ret;
struct sockaddr_in sin;//服务器地址结构
//一、初始化监听套接字
if((fd=socket(AF_INET,SOCK_STREAM,0))<0){
perror("socket");
exit(1);
}
/*二、填充结构体sockaddr_in的结构体变量*/
bzero(&sin,sizeof(sin));//对结构体变量进行清零
sin.sin_family=AF_INET;
sin.sin_port=htons(SERV_PORT);//网络字节序端口号
if(inet_pton(AF_INET,SERV_IP_ADDR,&sin.sin_addr.s_addr)!=1)//把本地地址转为网络地址,如果不等于0说明转化失败
{
perror("inet_pton");
exit(1);
}
//sin.sin_addr.s_addr=inet_pton(SERV_IP_ADDR);
/*if(bind(fd,(struct sockaddr *)&sin,sizeof(sin))<0){//绑定
perror("bind");
exit(1);
}*/
if(connect(fd,(struct sockaddr *)&sin,sizeof(sin))<0)
{
perror("connect");
exit(1);
}
char buf[BUFSIZ];
while(1){
bzero(buf,BUFSIZ);
if(fgets(buf,BUFSIZ-1,stdin)==NULL){
continue;
}
write(fd,buf,strlen(buf));
if(!strncasecmp(buf,QUIT_STR,strlen(QUIT_STR)))//用户输入quit
{
printf("Client is exiting!\n");
break;
}
ret=read(fd,buf,sizeof(buf));
write(STDOUT_FILENO,buf,ret);
}
close(fd);
}
错误处理函数的封装
wrap.c:
#include "wrap.h"
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <sys/socket.h>
void perr_exit(const char *s)
{
perror(s);
exit(-1);
}
int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr)
{
int n;
again:
if ((n = accept(fd, sa, salenptr)) < 0) {
if ((errno == ECONNABORTED) || (errno == EINTR))
goto again;
else
perr_exit("accept error");
}
return n;
}
int Bind(int fd, const struct sockaddr *sa, socklen_t salen)
{
int n;
if ((n = bind(fd, sa, salen)) < 0)
perr_exit("bind error");
return n;
}
int Connect(int fd, const struct sockaddr *sa, socklen_t salen)
{
int n;
if ((n = connect(fd, sa, salen)) < 0)
perr_exit("connect error");
return n;
}
int Listen(int fd, int backlog)
{
int n;
if ((n = listen(fd, backlog)) < 0)
perr_exit("listen error");
return n;
}
int Socket(int family, int type, int protocol)
{
int n;
if ((n = socket(family, type, protocol)) < 0)
perr_exit("socket error");
return n;
}
ssize_t Read(int fd, void *ptr, size_t nbytes)
{
ssize_t n;
again:
if ( (n = read(fd, ptr, nbytes)) == -1) {
if (errno == EINTR)
goto again;
else
return -1;
}
return n;
}
ssize_t Write(int fd, const void *ptr, size_t nbytes)
{
ssize_t n;
again:
if ( (n = write(fd, ptr, nbytes)) == -1) {
if (errno == EINTR)
goto again;
else
return -1;
}
return n;
}
int Close(int fd)
{
int n;
if ((n = close(fd)) == -1)
perr_exit("close error");
return n;
}
/*参三: 应该读取的字节数*/
ssize_t Readn(int fd, void *vptr, size_t n)
{
size_t nleft; //usigned int 剩余未读取的字节数
ssize_t nread; //int 实际读到的字节数
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;
}
ssize_t 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;
else
return -1;
}
nleft -= nwritten;
ptr += nwritten;
}
return n;
}
wrap.h:
#ifndef _WRAP_H_
#define _WRAP_H_
#include <stdio.h>
#include <ctype.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr);
int Bind(int fd, const struct sockaddr *sa, socklen_t salen);
int Connect(int fd, const struct sockaddr *sa, socklen_t salen);
int Listen(int fd, int backlog);
int Socket(int family, int type, int protocol);
ssize_t Read(int fd, void *ptr, size_t nbytes);
ssize_t Write(int fd, const void *ptr, size_t nbytes);
int Close(int fd);
ssize_t Readn(int fd, void *vptr, size_t n);
ssize_t Writen(int fd, const void *vptr, size_t n);
ssize_t Readline(int fd, void *vptr, size_t maxlen);
#endif
并发服务器
多进程并发服务器设计
服务端:
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include "wrap.h"
#include <signal.h>
#include <sys/wait.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <sys/types.h>
#include <sys/socket.h>
#define SERV_PORT 5001
#define SERV_IP_ADDR "192.168.1.105"
#define QUIT_STR "quit"
void catch_child(int signum)//回收子进程
{
while(waitpid(0,NULL,WNOHANG)>0);
return ;
}
int main(void)
{
int fd=-1,cfd;
pid_t pid;
char buf[BUFSIZ];
int ret;
struct sockaddr_in serv_addr,clt_addr;
socklen_t clt_addr_len;
bzero(&serv_addr,sizeof(serv_addr));//清除结构体的内容
serv_addr.sin_family=AF_INET;
serv_addr.sin_port=htons(SERV_PORT);
serv_addr.sin_addr.s_addr=htonl(INADDR_ANY);
fd=Socket(AF_INET,SOCK_STREAM,0);
Bind(fd,(struct sockaddr *)&serv_addr,sizeof(serv_addr));
Listen(fd,128);
clt_addr_len=sizeof(clt_addr);
while(1){
cfd=Accept(fd,(struct sockaddr*)&clt_addr,&clt_addr_len);
if((pid=fork())<0){
perror("fork");
exit(1);
}else if(pid==0){
close(fd);
break;
}else{
//父进程处理
struct sigaction act;
act.sa_handler=catch_child;
sigemptyset(&act.sa_mask);
act.sa_flags=0;
ret=sigaction(SIGCHLD,&act,NULL);
if(ret!=0){
perror("sigaction");
exit(1);
}
close(cfd);
continue;
}
}
if(pid==0){
//子进程
for(;;){
ret=read(cfd,buf,sizeof(buf));
if(ret==0){
close(cfd);
exit(1);
}
write(cfd,buf,ret);
write(STDOUT_FILENO,buf,ret);
}
}
return 0;
}
多线程并发服务器
服务端:
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <pthread.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <sys/types.h>
#include <sys/socket.h>
#include "wrap.h"
#define SERV_PORT 5001
#define QUIT_STR "quit"
#define MAXLINE 8192
void *do_work(int arg)//子线程处理函数
{
int n,i;
char arr[256];
while(1){
n=read(arg,arr,MAXLINE);
if(n==0){
printf("the client %d closed...\n",arg);
break;
}
write(STDOUT_FILENO,arr,n);
write(arg,arr,n);
}
}
int main()
{
int fd;
char buf[MAXLINE];
socklen_t clt_addr_len;
pthread_t tid;
//struct s_info ts[256];
int newfd[256];
int i=0;
struct sockaddr_in servaddr,cltaddr;
fd=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(SERV_PORT);
Bind(fd,(struct sockaddr*)&servaddr,sizeof(servaddr));
Listen(fd,128);
printf("Accepting Client connected ...\n");
clt_addr_len=sizeof(cltaddr);
while(1)//主线程
{
newfd[i]=Accept(fd,(struct sockaddr*)&cltaddr,&clt_addr_len);
if(pthread_create(&tid,NULL,do_work,newfd[i])==-1){
perror("pthread_create");
exit(1);
}
pthread_detach(tid);
i++;
}
return 0;
}
不积小流无以成江河,不积跬步无以至千里。而我想要成为万里羊,就必须坚持学习来获取更多知识,用知识来改变命运,用博客见证成长,用行动证明我在努力。
如果我的博客对你有帮助、如果你喜欢我的博客内容,记得“点赞” “评论” “收藏”一键三连哦!听说点赞的人运气不会太差,每一天都会元气满满呦!如果实在要白嫖的话,那祝你开心每一天,欢迎常来我博客看看。
转载:https://blog.csdn.net/weixin_44895651/article/details/108163533