socket之TCP通信

1、TCP编程模型:
服务器端:socket(创建套接字)–>bind(绑定套接字)–>listen(设计套接字监听数量)–>accept(等待客服端访问)–>read/write(对客服端进行读写操作)–>close(关闭套接字)

客服端: socket(创建套接字)—>connect(连接服务器)–>-->read/write(对服务器进行读写)–>close(关闭套接字)

2、socket函数原型:
int socket(int domain,int type, int protocol)

domain:指明了协议族/域,通常AF_INET、AF_INET6、AF_LOCAL等;
type:是套接口类型,主要SOCK_STREAM(TCP)、SOCK_DGRAM(UDP)、SOCK_RAW(原始socket);
protocol:一般取为0。成功时,返回一个小的非负整数值,与文件描述符类似
返回值:非负描述符 – 成功,-1 - 出错

3、bind函数原型:
int bind(int sockfd, const struct sockaddr * my_addr, socklen_t addrlen);
函数功能:把一个本地协议地址赋予一个套接字。对于网际协议,协议地址是32位的IPv4地址或是128位的IPv6地址与16位的TCP或UDP端口号的组合。 对于IPv4来说,通配地址通常由INADDR_ANY来指定,其值一般为0。它告知内核去选择IP地址。
sockfd: 套接字编号
my_addr: 是一个指向sockaddr结构体类型的指针
addrlen: 表示my_addr结构的长度,可以用sizeof操作符获得。
返回值: 返回值:成功返回0,失败返回-1

4、listen函数原型:
int listen(int sockfd, int backlog);
函数功能:主动连接套接字变为被连接套接口,使得一个进程可以接受其它进程的请求,从而成为一个服务器进程。在TCP服务器编程中listen函数把进程变为一个服务器,并指定相应的套接字变为被动连接。
sockfd :一个已绑定未被连接的套接字描述符
backlog: 连接请求队列(queue of pending connections)的最大长度(一般由2到4)。用SOMAXCONN则由系统确定。

5、accept函数原型:
SOCKET accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
函数功能:accept()是在一个套接口接受的一个连接。本函数从s的等待连接队列中抽取第一个连接,创建一个与s同类的新的套接口并返回句柄。
sockfd:套接字描述符,该套接口在listen()后监听连接。
addr:(可选)指针,指向一缓冲区,其中接收为通讯层所知的连接实体的地址。Addr参数的实际格式由套接口创建时所产生的地址族确定。
addrlen:(可选)指针,输入参数,配合addr一起使用,指向存有addr地址长度的整型数。
返回值: socket编号
注意:对应两个队列:一个是已完成的连接队列;另一个是未完成的连接队列。最大数指的就是已完成的连接队列最大数。

6、connect函数原型:
int connect(int soctfd, const struct sockaddr * addr, int addrlen);
函数功能: 用于建立与指定socket的连接。
soctfd:标识一个未连接socket
addr:指向要连接套接字的sockaddr结构体的指针
addrlen:sockaddr结构体的字节长度
返回值: 成功0;失败-1
a. 硬错:端口号错误,服务器进程未开启,收到RST,立刻返回ECONNREFUSED;
b. 软错:IP不可达,协议ICMP,比如no route to host,通常是发送arp请求无响应。

7、端口复用
在网络通信中,一个端口只能被一个进程使用,不能多个进程共用同一个端口。我们在进行套接字通信的时候,如果按顺序执行如下操作:先启动服务器程序,再启动客户端程序,然后关闭服务器进程,再退出客户端进程,最后再启动服务器进程,就会出如下的错误提示信息:bind error: Address already in use
举例端口port为:10000。 server为服务端程序编译后的可执行文件,当执行出现“bind error: Address already in use”,使用shell指令查看TCP状态,如下所示:

# 第二次启动服务器进程
$ ./server 
bind error: Address already in use

$ netstat -apn|grep 10000
(Not all processes could be identified, non-owned process info
 will not be shown, you would have to be root to see it all.)
tcp        0      0 127.0.0.1:10000          127.0.0.1:50178         TIME_WAIT 

通过 netstat 查看 TCP 状态,发现上一个服务器进程其实还没有真正退出。因为服务器进程是主动断开连接的进程,最后状态变成了 TIME_WAIT 状态,这个进程会等待 2msl(大约1分钟) 才会退出,如果该进程不退出,其绑定的端口就不会释放,再次启动新的进程还是使用这个未释放的端口,端口被重复使用,就是提示 bind error: Address already in use 这个错误信息。

如果想要解决上述问题,需使用setsockopt函数设置端口复用,该函数使用在服务端,在bind函数前使用。使用的函数原型如下:
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
函数功能:设置套接字选项
sockfd:用于监听的文件描述符
level:设置端口复用需要使用 SOL_SOCKET 宏
optname:要设置什么属性(SO_REUSEADDR/SO_REUSEPORT这两个宏都可设置端口复用)
optval:设置是去除端口复用属性还是设置端口复用属性,使用 int 型变量设置(0:不设置 ;1:设置)
optlen:optval 指针指向的内存大小 sizeof (int)
返回值:成功:0;失败:-1

8、socket头文件:

#include <sys/socket.h>
#include <sys/types.h>
#include <sys/poll.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <fcntl.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <pthread.h>

9、客服端多线程读写例子:

#define BUF_SIZE 100
#define SERVER_IP "192.168.117.128"	//IP号之间不能有空格

int socketfd = 0;

void FuncExit(int signal)
{
	printf("socketC exit!\n");
	close(socketfd);		//关闭服务器
	exit(0);
}

void *func(void *arg)
{
	int byte;
	char *buf = (char *)arg;
	
	while(1)
	{
		memset(buf, 0, BUF_SIZE );
		byte = read(socketfd, buf, BUF_SIZE);	//读取server数据,有数据更新才读取,否则阻塞
		if(byte == 0)
		{
			perror("read over");
			exit(0);
		}
		if(byte < 0)
		{
			perror("read failed");
			pthread_exit(0);
		}
		printf("len:%ld %s",strlen(buf), buf);	
	}	
	exit(0);			//退出整个线程
}

int main(int argc, void *argv[] )
{
	
	struct sockaddr_in servaddr;
	
	signal(SIGINT, FuncExit);			//接收终端ctrl+C的信号执行FuncExit函数
	
	if( (socketfd = socket(AF_INET, SOCK_STREAM, 0) ) ==  -1)	//socket函数类似于open函数,打开并创建一个socket,AF_INET使用IPV4协议族,SOCK_STREAM为TCP套接口
	{
		perror("socket create failed!");
		return -1;
	}
	
	memset(&servaddr, 0, sizeof(servaddr) );
	servaddr.sin_family = AF_INET;
	servaddr.sin_addr.s_addr = inet_addr(SERVER_IP);
	servaddr.sin_port = htons(8888);
	if(connect(socketfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0 )
	{
		perror("socket connect failed!");
		return -1;
	}
	
	char rdbuf[100], wdbuf[100];
	pthread_t tid;	
	pthread_create(&tid, NULL, func, (void*)rdbuf);
	
	while(1)
	{
		memset(&wdbuf, 0, sizeof(wdbuf) );
		fgets(wdbuf, sizeof(wdbuf), stdin);
		write(socketfd, wdbuf, sizeof(wdbuf));			//发送数据到server		
	}	
	return 0;	
}

10、服务器多线程连接客服端例子:

#define MAX_LISTEN_QUE 100
#define BUF_SIZE 100

int num = 0;
int sockfd[MAX_LISTEN_QUE] = {0};

//线程退出处理函数
void FuncExit(int signal)
{
	int i = 0;
	for(i=0; i<=num; i++)
	close(sockfd[i]);

	printf("socketC exit!\n");
	exit(0);
}

//服务器接收客户端数据
void *ClientToServer(void *arg)
{
	int byte = 0;
	char rdbuf[BUF_SIZE] = {0};
	int *readfd = (int *)arg;
	
	while(1)
	{
		memset(rdbuf, 0, BUF_SIZE );
		byte = read(*readfd, rdbuf, BUF_SIZE);	//读取client数据,有数据更新才读取,否则阻塞
		if(byte == 0)							//客户端关闭时,读取数据个数为0
		{
			printf("sockfd:%d read over\n", *readfd);
			close(*readfd);	
			pthread_exit(0);
		}
		if(byte < 0)
		{
			perror("read failed");
			close(*readfd);	
			pthread_exit(0);
		}
		printf("sockfd:%d %s",*readfd, rdbuf);	
	}
}

//服务器向客户端发送数据
void *ServerToClient(void *arg)
{
	char wdbuf[BUF_SIZE] = {0};
	int timep;
	int *writefd = (int*)arg;
	
	struct tcp_info info; 
	int len=sizeof(info); 
	
	while(1)
	{
		getsockopt(*writefd, IPPROTO_TCP, TCP_INFO, &info, (socklen_t *)&len); 
		if((info.tcpi_state==TCP_ESTABLISHED) )  							//判断客户端未断开  else 断开
		{
			memset(wdbuf, 0, BUF_SIZE );
			timep = time(NULL);
			snprintf(wdbuf, sizeof(wdbuf), "%s", ctime((time_t *)&timep));
			write(*writefd, wdbuf, strlen(wdbuf));		
			sleep(10);
		}
		else
		{
			printf("close socket\n");
			close(*writefd);		
		}			
	}
}

int main(int argc, void *argv[] )
{
	int listenfd, opt = 1;
	struct sockaddr_in server, client;
	socklen_t len;
	int ret;
	
	signal(SIGINT, FuncExit);			//接收终端ctrl+C的信号执行FuncExit函数
	
	listenfd = socket(AF_INET, SOCK_STREAM, 0);		//socket函数类似于open函数,打开并创建一个socket,AF_INET使用IPV4协议族,SOCK_STREAM为TCP套接口
	if(listenfd < 0)
	{
		perror("Create socket fail.");
		return -1;
	}	
	
	if( (ret = setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) ) < 0)	//设置IP地址端口可重用
	{
  		perror("Error, set socket reuse addr failed");	
  		return -1;
	}
	
	memset(&server, 0, sizeof(server));
	server.sin_family = AF_INET;					//IPV4协议
	server.sin_port   = htons(8888);				//端口号
	server.sin_addr.s_addr  = htonl(INADDR_ANY);	//自动生成自身IP做服务器IP给客户端连接
	
	len = sizeof(struct sockaddr);
	if(bind(listenfd, (struct sockaddr *)&server, len)<0)	//绑定
	{
		perror("bind error.");
		return -1;
	}
	
	listen(listenfd, MAX_LISTEN_QUE);		//设置最大监听数
	
	pthread_t tid[MAX_LISTEN_QUE];
	
	while(1)
	{
		sockfd[num] = accept(listenfd, (struct sockaddr *)&client, &len);	//等待客户端连接,没有则挂起睡眠
		if(sockfd < 0)
		{
			perror("accept error.");
			return -1;
		}
		pthread_create(&tid[num], NULL, ServerToClient, (void*)&sockfd[num]);
		pthread_create(&tid[num], NULL, ClientToServer, (void*)&sockfd[num]);
		
		num++;
		printf("num:%d\n", num);
	}
	return 0;

}
  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值