tcp连接默认为双向通信,发送成功回调。
一、实现
服务器
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
//#include <sys/socket.h>
#include <netinet/in.h>
//#include <arpa/inet.h>
#include <pthread.h>
#include<stdlib.h>
//TCP通信
typedef struct
{
struct sockaddr_in client_addr;//存放客户端ip+port
int con_fd;//存放服务器和客户端通信的通信套接字
}client_addr_table;
void *run (void *arg)
{
/*1.linux线程执行和windows不同,pthread有两种状态joinable状态和unjoinable状态,
如果线程是joinable状态,当线程函数自己返回退出时或pthread_exit时
都不会释放线程所占用堆栈和线程描述符(总计8K多)。
只有当你调用了pthread_join之后这些资源才会被释放。
若是unjoinable状态的线程,这些资源在线程函数退出时或pthread_exit时自动会被释放。
2.unjoinable属性可以在pthread_create时指定,
或在线程创建后在线程中pthread_detach自己, 如:pthread_detach(pthread_self()),
将状态改为unjoinable状态,确保资源的释放。
或者将线程置为 joinable,然后适时调用pthread_join.
3.其实简单的说就是在线程函数头加上 pthread_detach(pthread_self())的话,
线程状态改变,在函数尾部直接 pthread_exit线程就会自动退出。省去了给线程擦屁股的麻烦。
pthread_join()即是子线程合入主线程,主线程阻塞等待子线程结束,然后回收子线程资源
*/
pthread_detach(pthread_self());//分离当前线程,就不用主线程去结合
client_addr_table *p=(client_addr_table *)arg;
//聊天
char buf[100];
char buffer[100]={"send finish"};
while(1)
{
bzero(buf,100);
read(p->con_fd,buf,100);
if(!strncmp(buf,"quit",4))
{
break;
}
printf("[%s][%d]:%s\n",
inet_ntoa(p->client_addr.sin_addr),ntohs(p->client_addr.sin_port),buf);
write(p->con_fd,buffer,strlen(buffer));//写数据
}
close(p->con_fd);
printf("[%s][%d]客户端退出\n",
inet_ntoa(p->client_addr.sin_addr),ntohs(p->client_addr.sin_port));
}
int main(void)
{
/*
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
返回值:非负描述符 – 成功,-1 - 出错
1) domain为地址族(Address Family),也就是 IP 地址类型,
常用的有 AF_INET 和 AF_INET6。AF 是“Address Family”的简写,INET是“Inetnet”的简写。
AF_INET 表示 IPv4 地址,例如 127.0.0.1;
AF_INET6 表示 IPv6 地址,例如 1030::C9B4:FF12:48AA:1A2B。
127.0.0.1,它是一个特殊IP地址,表示本机地址。
2) type 为数据传输方式/套接字类型,
常用的有 SOCK_STREAM(流格式套接字/面向连接的套接字)
和 SOCK_DGRAM(数据报套接字/无连接的套接字)
3) protocol 表示传输协议,常用的有 IPPROTO_TCP 和 IPPTOTO_UDP,
分别表示 TCP 传输协议和 UDP 传输协议。
一般情况下有了 af 和 type 两个参数就可以创建套接字了,
操作系统会自动推演出协议类型,除非遇到这样的情况:
有两种不同的协议支持同一种地址类型和数据传输类型。
如果我们不指明使用哪种协议,操作系统是没办法自动推演的。
*/
//建立套接字
int tcp_fd=socket(AF_INET,SOCK_STREAM,0);
if(tcp_fd<0)
{
printf("socket fail\n");
return -1;
}
else
{
printf("socket......\n");
printf("socket success\n");
}
/*struct sockaddr和struct sockaddr_in这两个结构体用来处理网络通信的地址。
一、sockaddr
sockaddr在头文件#include <sys/socket.h>中定义,
sockaddr的缺陷是:sa_data把目标地址和端口信息混在一起
struct sockaddr
{
sa_family_t sin_family;//地址族
char sa_data[14]; //14字节,包含套接字中的目标地址和端口信息
};
二、sockaddr_in
sockaddr_in在头文件#include<netinet/in.h>或#include <arpa/inet.h>中定义,
该结构体解决了sockaddr的缺陷,把port和addr 分开储存在两个变量中
struct sockaddr_in
{
short sin_family;
//Address family一般来说AF_INET(地址族)PF_INET(协议族
unsigned short sin_port;
//Port number(必须要采用网络数据格式,
//普通数字可以用htons()函数转换成网络数据格式的数字)
struct in_addr sin_addr;
//IP address in network byte order(Internet address)
unsigned char sin_zero[8];
//Same size as struct sockaddr没有实际意义,只是为了 跟SOCKADDR结构在内存中对齐
};
//存放32IP地址
typedef uint32_t in_addr_t;
struct in_addr
{
in_addr_t s_addr;
};
*/
struct sockaddr_in server_addr;
server_addr.sin_family=AF_INET;
/*htons 转换程网络字节序
#include <arpa/inet.h>
uint16_t htons(uint16_t hostshort);
将一个无符号短整型数值转换为网络字节序,即大端模式(big-endian),电脑存储小端序
https://www.zhihu.com/question/54580649/answer/145500997
端口的作用:
我们知道一台主机(对应一个IP地址)可以提供很多服务,比如web服务,ftp服务等等。
如果只有一个IP,无法却分不同的网络服务,所以我们采用”IP+端口号”来区分不同的网络服务。
端口的定义:
端口号是标识主机内唯一的一个进程,IP+端口号就可以标识网络中的唯一进程。
在我们通常用的Socket编程中,IP+端口号就是套接字
端口号是由16比特进行编号,范围是0-65535,按照道理来讲,这些端口你都可以随便用。
但是你不是vip用户,所以有一些端口被vip用户占着。
比如FTP 21 Ssh 22等等,所以给端口分了类,规定你可以使用端口的范围。
端口的分类
分类的维度很多,这里我们按照服务端使用还是客户端使用分类
a.服务端使用的端口号 预留端口号
取值范围0-1023,这些端口我们编程的时候不能使用,是那些vip应用程序使用的,
只有超级用户特权的应用才允许被分配一个预留端口号
登记端口号
取值范围1024-49151,就是我们平时编写服务器使用的端口号范围 ,
服务器的端口号,只要在这个范围就好.
客户端使用的端口号
取值范围49152-65535,这部分是客户端进程运行时动态选择的范围,又叫临时端口号*/
server_addr.sin_port=htons(43281);
/* #include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
将字符串转换成32位的网络字节序
char *inet_ntoa(struct in_addr in);
tcp_addr.sin_addr.s_addr=inet_addr(INADDR_ANY)
宏INADDR_ANY转换过来就是0.0.0.0,泛指本机的意思,也就是表示本机的所有IP,
因为有些机子不止一块网卡,多网卡的情况下,这个就表示所有网卡ip地址的意思。
就比方说我这里本机的ip地址包括:
192.168.150.136 ifconfig
127.0.0.1
0.0.0.0
*/
server_addr.sin_addr.s_addr=inet_addr("192.168.150.136");
/*bind函数把一个本地协议地址赋予一个套接字。
#include<sys/socket.h>
int bind(int sockfd, const struct sockaddr, socklen_t addrlen);
第二个参数是一个指向特定协议的地址结构的指针,第三个参数是该地址结构的长度。*/
bind(tcp_fd, (struct sockaddr *)&server_addr,sizeof(struct sockaddr));
/*头文件:#include <sys/socket.h>
定义函数:int listen(int s, int backlog);
函数说明:listen()用来等待参数s 的socket 连线.
参数backlog 指定同时能处理的最大连接要求,
如果连接数目达此上限则client 端将收到ECONNREFUSED 的错误.
Listen()并未开始接收连线, 只是设置socket 为listen 模式,
真正接收client 端连线的是accept().
通常listen()会在socket(), bind()之后调用, 接着才调用accept().
*/
listen(tcp_fd,4);
int con_fd;
struct sockaddr_in client_addr;
socklen_t client_length=sizeof(struct sockaddr);//存放客户端结构体地址大小的变量
int i=0;
client_addr_table client_table[100];
while(1)
{
/*当套接字处于监听状态时,可以通过 accept() 函数来接收客户端请求。
int accept(int sock, struct sockaddr *addr, socklen_t *addrlen);*/
con_fd=accept(tcp_fd,(struct sockaddr *)&client_addr,&client_length);//建立通信套接字
if(con_fd<0)
{
printf("accept failed\n");
return -2;
}
else
{
printf("connect\n");
}
client_table[i].client_addr=client_addr;
client_table[i].con_fd=con_fd;
pthread_t tcp_thread;
printf("[%s][%d]客户端连接成功\n",
inet_ntoa(client_addr.sin_addr),ntohs(client_addr.sin_port));
/*函数简介
pthread_create是UNIX环境创建线程函数
在默认情况下通过pthread_create函数创建的线程是非分离属性的,
由pthread_create函数的第二个参数决定,在非分离的情况下,
当一个线程结束的时候,它所占用的系统资源并没有完全真正的释放,也没有真正终止。
头文件
#include<pthread.h>
函数声明
int pthread_create(pthread_t *restrict tidp,
const pthread_attr_t *restrict_attr,
void*(*start_rtn)(void*),
void *restrict arg);
返回值 若成功则返回0,否则返回出错编号
参数
第一个参数为指向线程标识符的指针。
第二个参数用来设置线程属性。
第三个参数是线程运行函数的地址。
最后一个参数是运行函数的参数。
*/
int ret=pthread_create(&tcp_thread, NULL,run, (void *)&client_table[i]);
if(ret != 0)
{
printf("creat pthread fail\n");
}
/*创建线程分离属性
pthread_attr_t ta;
pthread_attr_init(&ta);
//设置线程为分离状态
pthread_attr_setdetachstate(&ta, PTHREAD_CREATE_DETACHED);
ret = pthread_create(&ivi_rxtid, &ta, (void *)run,(void *)&client_table[i]);
if (ret != 0)//创建失败
{
printf("creat pthread fail\n");
}*/
i++;
}
close(con_fd);
close(tcp_fd);
return 0;
}
客户端
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
//#include <sys/socket.h>
#include <netinet/in.h>
//#include <arpa/inet.h>
#include <pthread.h>
void *read_server(void *arg)
{
int sockfd=*((int *)arg);
char buf[100];
while(1)
{
bzero(buf,100);
read(sockfd,buf,100);
printf("server:%s\n",buf);
}
}
int main(void)
{
int tcp_fd=socket(AF_INET,SOCK_STREAM,0);//建立带连接套接字
if(tcp_fd<0)
{
printf("socket fail\n");
return -1;
}
else
{
printf("socket......\n");
printf("socket success\n");
}
//拨号,服务器的ip+port
struct sockaddr_in server_addr;
server_addr.sin_family=AF_INET;
server_addr.sin_port=htons(43281);//转换程网络字节序
server_addr.sin_addr.s_addr=inet_addr("192.168.150.136");//将字符串转换成32位的网络字节序
/*#include<sys/socket.h>
int connect(int sockfd, const struct sockaddr *servaddr, int *addrlen);
返回:若成功则返回0,失败则返回-1;
sockfd是有socket函数返回的套接字描述符,
第二个、第三个参数分别是一个指向套接字地址结构的指针和该结构的大小。
套接字地址结构必须含有服务器的IP地址和端口号。*/
int n=connect(tcp_fd, (struct sockaddr *)&server_addr,sizeof(struct sockaddr));
if(n<0)
{
printf("connect failed\n");
return -2;
}
else
{
printf("connect\n");
}
//创建一个子线程
pthread_t read_thread;
pthread_create(&read_thread, NULL, read_server, (void *)&tcp_fd);
//聊天
char buf[100];
while(1)
{
bzero(buf,100);
fgets(buf,100,stdin);
write(tcp_fd,buf,strlen(buf)); //给服务器发送消息
if(!strncmp(buf,"quit",4))
{
break;
}
}
close(tcp_fd);
return 0;
}
二、效果
服务器
客户端