实现TCP通信(socket套接字)

一、TCP通信实现的过程

在这里插入图片描述

服务器端

  1. socket函数 与 通信域
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
-domain: 指定通信域(通信地址族);
-type: 指定套接字类型;
-protocol: 指定协议;

-domain: 指定通信域(通信地址族)
AF_INET: 使用IPv4 互联网协议
AF_INET6: 使用IPv6 互联网协议

-type:指定套接字类型
TCP唯一对应流式套接字,所以选择SOCK_STREAM(数据报套接 字:SOCK_DGRAM)
-protocol:指定协议
流式套接字唯一对应TCP,所以无需要指定协议,设为0即可

  1. bind函数 与 通信结构体
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
-sockfd:socket函数生成的套接字
-addr:通信结构体
-addrlen:通信结构体的长度

IPv4地址族结构体

struct sockaddr_in {
sa_family_t    sin_family; /* 地址族: AF_INET */
in_port_t      sin_port;   /* 网络字节序的端口号 */
struct in_addr sin_addr;   /*IP地址结构体 */
};
/* IP地址结构体 */
struct in_addr {
uint32_t       s_addr;     /* 网络字节序的IP地址 */
};

通用地址族结构体

struct sockaddr {
         sa_family_t sa_family;
         char        sa_data[14];
 }

示例:为套接字fd绑定通信结构体addr

addr.sin_family = AF_INET;
addr.sin_port = htons(5001);
addr.sin_addr.s_addr = 0;
bind(fd, (struct sockaddr *)&addr, sizeof(addr) );
  1. 监听套接字
int listen(int sockfd, int backlog);
  1. 处理客户端发起的连接,生成新的套接字
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
-sockfd: 函数socket生成的套接字
-addr:客户端的地址族信息
-addrlen:地址族结构体的长度

实现代码:
server.c

#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>

#define BACKLOG 5

int main(int argc, char *argv[])
{
	int fd, newfd, ret;
	char buf[BUFSIZ] = {}; //BUFSIZ 8142
	struct sockaddr_in addr;
	
	if(argc < 3){
		fprintf(stderr, "%s<addr><port>\n", argv[0]);
		exit(0);
	}

	/*1. 创建套接字*/
	fd = socket(AF_INET, SOCK_STREAM, 0);
	if(fd < 0){
		perror("socket");
		exit(0);
	}
	addr.sin_family = AF_INET;
	/*main()传参的方式给定端口号
	  因为main()函数中的 char *argv[] ,终端输入的argv是字符串,所以要用 atoi() 函数将字符转换成数字
	  uint16_t htons(uint16_t hostshort);  字节序转换函数(本机转网络字节序)
	*/
	addr.sin_port = htons( atoi(argv[2]) );
	/*
	      int inet_aton(const char *cp, struct in_addr *addr);  IP地址序转换函数
	      用于将一个点分十进制的 IPv4 地址字符串转换为网络字节序的二进制表示
	*/
	if ( inet_aton(argv[1], &addr.sin_addr) == 0) {
		fprintf(stderr, "Invalid address\n");
		exit(EXIT_FAILURE);
	}


	/*
	当你先关闭服务器端,没有关闭客户端的情况下,系统会认为当前的端口号和IP地址还在链接当中,下次再用该端口号和IP地址运行的情况下系统会报错:bind: Address already in use

	下面这段代码的用处就是解决这个问题
	*/
	/*地址快速重用*/
	int flag=1,len= sizeof (int); 
	if ( setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &flag, len) == -1) 
	{ 
		     perror("setsockopt"); 
			 exit(1); 
	} 
	
	
	/*2. 绑定通信结构体*/
	if(bind(fd, (struct sockaddr *)&addr, sizeof(addr) ) == -1){
		perror("bind");
		exit(0);
	}
	/*3. 设置套接字为监听模式*/
	if(listen(fd, BACKLOG) == -1){
		perror("listen");
		exit(0);
	}
	/*4. 接受客户端的连接请求,生成新的用于和客户端通信的套接字*/
	newfd = accept(fd, NULL, NULL);
	if(newfd < 0){
		perror("accept");
		exit(0);
	}
	/*可以循环读入信息*/
	while(1){
		memset(buf, 0, BUFSIZ);//每次使用完地址,都要清空buf内容
		/*
		read()函数 是将客户端输入的信息接收,如果read()返回0时证明文件读到末尾了
		*/
		ret = read(newfd, buf, BUFSIZ);
		if(ret < 0)
		{
			perror("read");
			exit(0);
		}
		else if(ret == 0)
			break;
		else
			printf("buf = %s\n", buf);
	}
	close(newfd);
	close(fd);
	return 0;
}

客户端

client.c

#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>

#define BACKLOG 5

int main(int argc, char *argv[])
{
	int fd;
	struct sockaddr_in addr;
	char buf[BUFSIZ] = {};

	if(argc < 3){
		fprintf(stderr, "%s<addr><port>\n", argv[0]);
		exit(0);
	}

	/*创建套接字*/
	fd = socket(AF_INET, SOCK_STREAM, 0);
	if(fd < 0){
		perror("socket");
		exit(0);
	}

	addr.sin_family = AF_INET;
	addr.sin_port = htons( atoi(argv[2]) ); 
	if ( inet_aton(argv[1], &addr.sin_addr) == 0) {//main()函数传参的方式给定
		fprintf(stderr, "Invalid address\n");
		exit(EXIT_FAILURE);
	}

	/*向服务端发起连接请求*/
	if(connect(fd, (struct sockaddr *)&addr, sizeof(addr) ) == -1){
		perror("connect");
		exit(0);
	}
	/*循环输入信息*/
	while(1){
		printf("Input->");
		/*
		fgets() 函数从标准输入中读取一行数据,并将其存储在字符数组 buf 中。
		stdin,用于从标准输入读取数据。
		char *fgets(char *str, int size, FILE *stream);
		*/
		fgets(buf, BUFSIZ, stdin);
		/*
		write() 是一个系统调用函数,用于将数据从缓冲区写入到文件描述符所代表的文件或设备中。
		ssize_t write(int fd, const void *buf, size_t count);
		*/
		write(fd, buf, strlen(buf) );
	}
	close(fd);
	return 0;
}

为了方便编译代码写了一个简单的Makefile

CC= gcc
CFLAGS = -Wall
all: server client

server:server.c
	${CC} ${CFLAGS} -o server.c server
client:client.c
	${CC} ${CFLAGS} -o client.c client

运行代码截图:
在这里插入图片描述

二、实现TCP多进程并发

改动代码:

服务器端

server.c

#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>

#define BACKLOG 5
void SigHandle(int sig){
	if(sig == SIGCHLD){
		printf("client exited\n");
		wait(NULL);
	}
}
void ClinetHandle(int newfd);

int main(int argc, char *argv[])
{
	int fd, newfd;
	struct sockaddr_in addr, clint_addr;
	socklen_t addrlen = sizeof(clint_addr);
#if 0
/*
两种解决僵尸进程的方法
若子进程先结束
父进程如果没有及时回收,子进程变成僵尸进程

方法:1
*/
	struct sigaction act;
	act.sa_handler = SigHandle;
	act.sa_flags = SA_RESTART;
	sigemptyset(&act.sa_mask);
	sigaction(SIGCHLD, &act, NULL);
#else
/*方法:2*/
	signal(SIGCHLD, SigHandle);
#endif


	pid_t pid;
	if(argc < 3){
		fprintf(stderr, "%s<addr><port>\n", argv[0]);
		exit(0);
	}

	/*1. 创建套接字*/
	fd = socket(AF_INET, SOCK_STREAM, 0);
	if(fd < 0){
		perror("socket");
		exit(0);
	}
	addr.sin_family = AF_INET;
	addr.sin_port = htons( atoi(argv[2]) );
	
	if ( inet_aton(argv[1], &addr.sin_addr) == 0) {
		fprintf(stderr, "Invalid address\n");
		exit(EXIT_FAILURE);
	}
	
	int flag=1,len= sizeof (int); 
	if ( setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &flag, len) == -1) 
	{ 
		     perror("setsockopt"); 
			 exit(1); 
	} 
	
	
	/*2. 绑定通信结构体*/
	if(bind(fd, (struct sockaddr *)&addr, sizeof(addr) ) == -1){
		perror("bind");
		exit(0);
	}
	/*3. 设置套接字为监听模式*/
	if(listen(fd, BACKLOG) == -1){
		perror("listen");
		exit(0);
	}
	/*4. 接受客户端的连接请求,生成新的用于和客户端通信的套接字*/
	newfd = accept(fd, NULL, NULL);
	if(newfd < 0){
		perror("accept");
		exit(0);
	}
	/*可以循环读入信息*/
	while(1){
		/*
		接受客户端的连接请求,生成新的用于和客户端通信的套接字
		*/
		newfd = accept(fd, (struct sockaddr *)&clint_addr, &addrlen);
		if(newfd < 0){
			perror("accept");
			exit(0);
		}
		/*
		inet_ntoa() 函数用于将 client_addr.sin_addr 转换为字符串形式的 IP 地址。
		ntohs() 函数用于将网络字节序(大端序)的端口号转换为主机字节序(主机的字节序,通常是小端序)。
		*/
		printf("addr:%s port:%d\n", inet_ntoa(clint_addr.sin_addr), ntohs(clint_addr.sin_port) );
		if( (pid = fork() ) < 0){
			perror("fork");
			exit(0);
		}else if(pid == 0){ //子进程
			close(fd);
			ClinetHandle(newfd);
			exit(0);
		}
		else//父进程
			close(newfd);
	}
	close(fd);
	return 0;
}

void ClinetHandle(int newfd){
	int ret;
	char buf[BUFSIZ] = {};
	while(1){
		//memset(buf, 0, BUFSIZ);
		bzero(buf, BUFSIZ);
		ret = read(newfd, buf, BUFSIZ);
		if(ret < 0)
		{
			perror("read");
			exit(0);
		}
		else if(ret == 0)
			break;
		else
			printf("buf = %s\n", buf);
	}
	close(newfd);
}

客户端

client.c

#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>

#define BACKLOG 5

int main(int argc, char *argv[])
{
	int fd;
	struct sockaddr_in addr;
	char buf[BUFSIZ] = {};

	if(argc < 3){
		fprintf(stderr, "%s<addr><port>\n", argv[0]);
		exit(0);
	}

	/*创建套接字*/
	fd = socket(AF_INET, SOCK_STREAM, 0);
	if(fd < 0){
		perror("socket");
		exit(0);
	}

	addr.sin_family = AF_INET;
	addr.sin_port = htons( atoi(argv[2]) ); 
	if ( inet_aton(argv[1], &addr.sin_addr) == 0) {
		fprintf(stderr, "Invalid address\n");
		exit(EXIT_FAILURE);
	}

	/*向服务端发起连接请求*/
	if(connect(fd, (struct sockaddr *)&addr, sizeof(addr) ) == -1){
		perror("connect");
		exit(0);
	}
	/*循环输入信息*/
	while(1){
		printf("Input->");
		fgets(buf, BUFSIZ, stdin);
		write(fd, buf, strlen(buf) );
	}
	close(fd);
	return 0;
}

二、TCP并发多线程

服务器端

server.c

#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <strings.h>
#include <pthread.h>

#define BACKLOG 5

void *ClinetHandle(void *arg);
int main(int argc, char *argv[])
{
	int fd, newfd;
	struct sockaddr_in addr, clint_addr;
	pthread_t tid;
	socklen_t addrlen = sizeof(clint_addr);
	
	if(argc < 3){
		fprintf(stderr, "%s<addr><port>\n", argv[0]);
		exit(0);
	}

	/*创建套接字*/
	fd = socket(AF_INET, SOCK_STREAM, 0);
	if(fd < 0){
		perror("socket");
		exit(0);
	}
	addr.sin_family = AF_INET;
	addr.sin_port = htons( atoi(argv[2]) );
	if ( inet_aton(argv[1], &addr.sin_addr) == 0) {
		fprintf(stderr, "Invalid address\n");
		exit(EXIT_FAILURE);
	}

	/*地址快速重用*/
	int flag=1,len= sizeof (int); 
	if ( setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &flag, len) == -1) { 
		      perror("setsockopt"); 
			        exit(1); 
	} 
	/*绑定通信结构体*/
	if(bind(fd, (struct sockaddr *)&addr, sizeof(addr) ) == -1){
		perror("bind");
		exit(0);
	}
	/*设置套接字为监听模式*/
	if(listen(fd, BACKLOG) == -1){
		perror("listen");
		exit(0);
	}
	while(1){
		/*接受客户端的连接请求,生成新的用于和客户端通信的套接字*/
		newfd = accept(fd, (struct sockaddr *)&clint_addr, &addrlen);
		if(newfd < 0){
			perror("accept");
			exit(0);
		}
		printf("addr:%s port:%d\n", inet_ntoa(clint_addr.sin_addr), ntohs(clint_addr.sin_port) );
		pthread_create(&tid, NULL, ClinetHandle, &newfd);
		pthread_detach(tid);
	}
	close(fd);
	return 0;
}
void *ClinetHandle(void *arg){
	int ret;
	char buf[BUFSIZ] = {};
	int newfd = *(int *)arg;
	while(1){
		//memset(buf, 0, BUFSIZ);
		bzero(buf, BUFSIZ);
		ret = read(newfd, buf, BUFSIZ);
		if(ret < 0)
		{
			perror("read");
			exit(0);
		}
		else if(ret == 0)
			break;
		else
			printf("buf = %s\n", buf);
	}
	printf("client exited\n");
	close(newfd);
	return NULL;
}

客户端

client.c

#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>

#define BACKLOG 5

int main(int argc, char *argv[])
{
	int fd;
	struct sockaddr_in addr;
	char buf[BUFSIZ] = {};

	if(argc < 3){
		fprintf(stderr, "%s<addr><port>\n", argv[0]);
		exit(0);
	}

	/*创建套接字*/
	fd = socket(AF_INET, SOCK_STREAM, 0);
	if(fd < 0){
		perror("socket");
		exit(0);
	}

	addr.sin_family = AF_INET;
	addr.sin_port = htons( atoi(argv[2]) );
	if ( inet_aton(argv[1], &addr.sin_addr) == 0) {
		fprintf(stderr, "Invalid address\n");
		exit(EXIT_FAILURE);
	}

	/*向服务端发起连接请求*/
	if(connect(fd, (struct sockaddr *)&addr, sizeof(addr) ) == -1){
		perror("connect");
		exit(0);
	}
	while(1){
		printf("Input->");
		fgets(buf, BUFSIZ, stdin);
		write(fd, buf, strlen(buf) );
	}
	close(fd);
	return 0;
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值