服务端采用了线程,可以同时提供给多台客户端连接
TCP的服务端------tcp_server.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <pthread.h>
struct client_info
{
char ip[16];
unsigned short port;
int sock;
};
void* comm_thr(void* arg);
int main()
{
//第一步:创建套接字
int sock_listen = socket(AF_INET, SOCK_STREAM, 0);
if(sock_listen == -1)
{
perror("socket");
return 1;
}
//设置套接字属性,以允许地址复用
int val = 1;
setsockopt(sock_listen, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val));
//第二步:绑定地址
struct sockaddr_in myaddr;
myaddr.sin_family = AF_INET;//指定地址家族
myaddr.sin_addr.s_addr = INADDR_ANY;//指定使用本机任意地址
//myaddr.sin_addr.s_addr = inet_addr("114.115.246.184");//指定IP地址
myaddr.sin_port = htons(3389);//指定端口号
if(-1 == bind(sock_listen, (struct sockaddr*)&myaddr, sizeof(myaddr)))
{
perror("bind");
return 1;
}
//第三步:监听
if(-1 == listen(sock_listen, 5))//第二个参数表示连接等待队列的长度
{
perror("listen");
return 1;
}
pthread_t tid;
struct client_info* ci = NULL;
while(1)
{
//第四步:接收客户端连接请求
struct sockaddr_in client_addr;
socklen_t addr_len = sizeof(client_addr);
int sock_conn = accept(sock_listen, (struct sockaddr*)&client_addr, &addr_len);
if(sock_conn == -1)
{
perror("accept");
return 1;
}
/*
accept 函数参数解读:
第一个参数为监听套接字描述符;
第二个参数为地址结构体(struct sockaddr_in)指针,用于接收客户端的地址信息,如果对客户端地址不感兴趣,就传 NULL;
第三个参数为地址结构体长度,用于接收客户端地址长度,如果对客户端地址不感兴趣,就传 NULL。
如果成功,accept 函数的返回值为这条连接对应的套接字(通常称为连接套接字),后面使用这个套接字进行收发数据。如果失败,其返回值为
-1。
调用 accept 函数时,如果没有任何客户端连接请求到来,该函数会阻塞调用线程直到成功接收到一个客户端连接请求或出现错误才返回。
*/
ci = malloc(sizeof(struct client_info));
if(ci == NULL)
{
perror("malloc");
exit(1);
}
strcpy(ci->ip, inet_ntoa(client_addr.sin_addr));
ci->port = ntohs(client_addr.sin_port);
ci->sock = sock_conn;
printf("\n客户端(%s:%d)已连接!\n", ci->ip, ci->port);
if(pthread_create(&tid, NULL, comm_thr, ci))
{
perror("pthread_create");
printf("\n客户端(%s:%d)已断开连接!\n", ci->ip, ci->port);
close(sock_conn);
free(ci);
continue;
}
}
//第七步:关闭监听套接字
close(sock_listen);
return 0;
}
void* comm_thr(void* arg)
{
struct client_info* ci = (struct client_info*)arg;
//线程资源释放
pthread_detach(pthread_self());
//第五步:收发数据
char data[1000] = "I am ok!";
int ret = send(ci->sock, data, sizeof(data), 0);
if(ret == -1)
{
perror("send");
}
ret = recv(ci->sock, data, sizeof(data) - 1, 0);
if(ret > 0)
{
data[ret] = '\0';
printf("\n客户端(%s:%d)说:%s\n", ci->ip, ci-> port, data);
}
//第六步:断开连接
close(ci->sock);
printf("\n客户端(%s:%d)已断开连接!\n", ci->ip, ci->port);
free(ci);
pthread_exit(NULL);
}
TCP的客户端 ------tcp_client.c
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
int main(int argc, char** argv)
{
//第一步:创建套接字
int sock = socket(AF_INET, SOCK_STREAM, 0);
if(sock == -1)
{
perror("sock");
return 1;
}
/*
// 第 2 步:绑定地址(IP + Port)(显式绑定)
// 这一步可以不做
struct sockaddr_in myaddr;
myaddr.sin_family = AF_INET; // 指定地址家族
myaddr.sin_addr.s_addr = INADDR_ANY; // 指定使用本机任意地址(0.0.0.0)
//myaddr.sin_addr.s_addr = inet_addr("124.71.188.13"); // 指定使用本机某个具体的 IP 地址
myaddr.sin_port = htons(6666); // 指定使用的端口号为 6666
if(-1 == bind(sock, (struct sockaddr*)&myaddr, sizeof(myaddr)))
{
perror("bind");
return 1;
}
*/
/*
inet_addr 函数的作用:将字符串形式的 IP 地址转换为无符号 32 位整数形式(网络字节序)。
htons 函数的作用:将某个短整数(short)从主机(host)字节序转换为网络(network)字节序。
*/
//第三步:连接服务器
struct sockaddr_in srv_addr;
srv_addr.sin_family = AF_INET;
srv_addr.sin_addr.s_addr = inet_addr(argv[1]);
srv_addr.sin_port = htons(atoi(argv[2]));
if(-1 == connect(sock, (struct sockaddr*)&srv_addr, sizeof(srv_addr)))
{
perror("connect");
return 1;
}
//第四步:收发数据
char data[100];
int ret = recv(sock, data, sizeof(data), 0);
if(ret == -1)
{
perror("recv");
}
else
{
printf("服务器说:%s\n", data);
printf("%ld\n", strlen(data));
}
while(1) sleep(1);
//第五步:断开连接
close(sock);
return 0;
}