小言_互联网的博客

Linux下网络编程(2)——TCP编程

489人阅读  评论(0)

       传输控制协议(英语: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
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场