稳定连接-打电话
一、实现步骤
1.创建socket
2.初始化协议地址簇
3.绑定(服务端需要)
4.监听(服务端需要)
5.接收连接
6.开始通讯
7.通讯完成关闭socket
二、socket套接字
1.相关结构
struct sockaddr_in { short int sin_family; //协议簇 unsigned short int sin_port; //端口号 struct in_addr sin_addr; //IP地址,设置时使用sin_addr.s_addr unsigned char sin_zero[8]; //保留空字节 }
2.相关函数
1.创建套接字
1.创建套接字 int socket(int domain.int type,int protocol); 2.domain-域名 AF_INET 表示IPV4地址; 3.type-类型 SOCK_STREAM 流式套接字; SCOK_DGRAM 数据报套接字; 4.protocol-协议编号; IPPROTO_TCP 表示tcp协议
2.int atoi(const char* str)
#include<stdlib>
功能:将字符串str转换成一个整数并返回一个结果。参数str以数字开头,当函数从str中读到非数字字符则结束转换并将结果返回。
3.绑定-服务端使用
int bind(int sockfd, const struct sockaddr *, socklen_t addrlen); //把一个本地协议地址赋予给一个套接字
4.监听-服务端使用
int listen(int sockfd, int backlog); netstat -aptn | grep 8585 //查看是否在监听 sudo lsof -i:8585//查看被监听的端口号
5.接收连接——服务端
int accept(int sockfd,struct sockaddr *addr,socklen_t *addrlen); //接收一个套接字中已建立的连接(服务端)
6.连接——客户端
int connect (int sockfd,struct sockaddr * serv_addr,int addrlen); //这里是根据服务端和客户端的端口号来判断的,端口号一样才能进行连接
7.发送数据
int send( SOCKET s, const char FAR *buf, int len, int flags );
8.接收数据
int recv( SOCKET s, char FAR *buf, int len, int flags );
9.主机字节序转网络字节序
u_short htons(u_short hostshort); //因为主机字节序是小端存储,而网络字节序是大端存储,所以需要转换
10.点分十进制字符串转网络字节序的整数 ip
unsigned long inet_addr(const char FAR* cp);
11.将网络传输的二进制数值转化为成点分十进制的ip地址
char *inet_ntoa(struct in_addr in);
12.将一个无符号短整型数从网络字节顺序转换为主机字节顺序
uint16_t ntohs(uint16_t netshort);
三、代码及调试
使用pthread.h库出现问题
解决方法
包含了线程的头文件<pthread.h>,可是编译的时候却报错“对pthread_create未定义的引用“,是因为pthread库不是Linux系统默认的库,连接时需要使用库libpthread.a,所以在使用pthread_create创建线程时,在编译中要加-lpthread参数:gcc -g sererver.c -o tcpServer -lpthread 加上这个以后编译成功!
1.服务端
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<pthread.h>
#include <unistd.h>//关闭套接字相关头文件 close()
void thread_run(void* arg);
int main(int argc,char* argv[])
{
//应为系统会自动传入当前文件的路径地址,所以加上IP地址和端口号,argc=3,argv[0]:可执行文件名
if(argc!=3)//调用可执行程序时,必须传递IP地址和端口号,argv[1]:IP地址,argv[2]:端口号
{
printf("please use : %s [IP] [port]\n",argv[0]);
return 1;
}
//创建套接字,一个句柄
int sock;
sock=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
//创建套接字失败
if(sock<0)
{
perror("creat socket errno!\n");
return 1;
}
//初始化协议簇地址
//创建服务器的sockaddr_in
struct sockaddr_in local;
//初始化协议簇
local.sin_family=AF_INET;//IPV4的地址
//初始化端口号
local.sin_port=htons(atoi(argv[2]));//主机字节序是大端存储,要转为网络字节序的小端存储
//初始化IP地址
local.sin_addr.s_addr=inet_addr(argv[1]);//,IP地址是一个点分十进制字符串,需要转换成网络字节序的整数ip
//服务端需要绑定
//将socket和协议簇绑定,也就是将端口号和IP地址加入到socket里面去
//bind-把一个本地协议地址簇赋予给一个套接字
if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0)
{
//输出错误信息
perror("bind errno!\n");
//关闭socket
close(sock);
return 1;
}
//服务端需要监听
if(listen(sock,10))
{
perror("listen errno!\n");
close(sock);
return 1;
}
//等待接收
printf("bind and listen success! wait accept ... \n");
//接收客户端的连接
//客户端的sockaddr_in
struct sockaddr_in clientSock;
//求出这个sockaddr_in的大小
socklen_t len=sizeof(clientSock);
//死循环来接收连接及数据-阻塞接收
while(1)
{
//accept是阻塞函数
//接收客户端套接字中已建立的连接(服务端)
int fd=accept(sock,(struct sockaddr*)&clientSock,&len);
//接收失败
if(fd<0)
{
perror("accept errno!\n");
close(sock);
return 1;
}
//接收到连接
printf("get connect\nip is : %s\nport is : %d\n",
inet_ntoa(clientSock.sin_addr),ntohs(clientSock.sin_port));
//6、通讯(用多线程来演示)
pthread_t id;
pthread_create(&id,NULL,thread_run,(void *)fd);
pthread_detach(id);
}
//关闭
close(sock);
return 0;
}
void thread_run(void* arg)
{
printf("create a new thread\n");
//fd表示连接的socket句柄,通过该变量可以进行网络通讯
int fd = (int)arg;
//接收数据的缓冲区
char buf[1024] = {};
//开始通讯
while(1)
{
//将buf全部初始化为0
memset(buf,0,sizeof(buf));
//接收数据
int ret=recv(fd,buf,sizeof(buf)-1,0);
//接收数据成功
if(ret > 0)
{
buf[ret] = '\0';
printf("client say : %s\n",buf);
}
memset(buf,0,sizeof(buf));
printf("please Enter:");
//清空缓冲区数据
fflush(stdout);
//输入要发送给客户端的数据
scanf("%s",buf);//scanf也是一个阻塞函数
ret = strlen(buf);
//6-2、发送数据
if(ret > 0)
{
send(fd,buf,strlen(buf) + 1,0);
}
}
}
2.客户端
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<sys/socket.h>//socket相关头文件
#include<netinet/in.h>//sockadd_in相关头文件
#include<arpa/inet.h>//大端转小端头文件
#include<pthread.h>//线程相关头文件
#include <unistd.h>//关闭套接字函数相关头文件 close()
int main(int argc,char* argv[])
{
if(argc!=3)//开始执行时,必须输入IP地址和端口号
{
printf("please use : %s [IP] [port]\n",argv[0]);
return 1;
}
//创建客户端socket
int sock;
sock=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
//判断socket是否创建成功
if(sock<0)
{
perror("creat sock errno!\n");
return 1;
}
//初始化协议簇
struct sockaddr_in client;
client.sin_family=AF_INET;
client.sin_port=htons(atoi(argv[2]));
client.sin_addr.s_addr=inet_addr(argv[1]);
//客户端不需要监听和绑定,直接连接即可
int ret=connect(sock,(struct sockaddr*)&client,sizeof(client));
//判断是否连接成功
if(ret<0)
{
perror("connect errno!\n");
close(sock);
return 1;
}
//连接成功
printf("connect success!\n");
//接收数据的数组
char buf[1024] = {};
//开始通讯
while(1)
{
memset(buf,0,sizeof(buf));
printf("please Enter:");
fflush(stdout);
//输入通讯内容
scanf("%s",buf);
ret = strlen(buf);
//发送数据给服务端
if(ret > 0)
{
//发送给服务端
send(sock,buf,strlen(buf) + 1,0);
//接收数据
int ret = recv(sock,buf,sizeof(buf) - 1,0);//0表示一次收发
if(ret > 0)
{
//忽略大小写比较字符串,比较参数3表示的n个字符
if(strncasecmp(buf,"quit",4) == 0)
{
printf("quit\n");
break;
}
buf[ret] = '\0';
printf("client say : %s\n",buf);
}
}
}
//关闭socket通道
close(sock);
}
3.gdb
因为调用可执行程序时需要输入参数,所以使用
gdb set args //设置多个参数