网络编程时,先将客户端程序中服务端的地址写作“127.0.0.1”,程序运行无误后,再改为服务端地址。
注:字节类型转化,网络当中传递最小单位为字节。
server.c程序:
void *recvsocket(void *arg) //接收client端socket数据的线程
{
char s[1024];
while(1)
{
memset(s, 0, sizeof(s));
int rc = recv(st, s, sizeof(s), 0);
if(rc <= 0) //如果返回小于等于0,代表socket已经关闭或者出错
break;
printf("%s\n", s);
}
return NULL;
}
void *sendsocket(void *arg) //发送server端socket数据的线程
{
int st = *(int *)arg;
char s[1024];
while(1)
{
memset(s, 0, sizeof(s));
read(STDIN_FILENO, s, sizeof(s)); //从键盘读取用户输入信息
send(st, s, strlen(s), 0);
}
return NULL;
}
int main(int arg, char *args[])
{
if(arg < 2)
{
return -1;
}
int port = atoi(args[1]);
int st = socket(AF_INET, SOCK_STREAM, 0); //初始化socket
int on = 1;
if(setsockopt(st, SOL_SOCKET, SO_REUSEADDR,&on, sizeof(on)) == -1)
{
printf("setsockoptfailed %s\n", strerror(errno));
return EXIT_FAILURE;
}
struct sockaddr_in addr; //定义一个IP地址的结构
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET; //设置addr结构的属性定位为TCP/IP地址
addr.sin_port = htons(port); //将本地字节顺序转化为网络字节数据
addr.sin_addr.s_addr = htonl(INADDR_ANY); //INADDR_ANY代表这个server上所有的地址
//将IP地址与server程序绑定
if(bind(st, (struct sockaddr *)&addr, sizeof(addr)) == -1)
{
printf("connectfailed %s\n", strerror(errno));
return EXIT_FAILURE;
}
//server端开始listen,
if(listen(st, 20) == -1)
{
printf("listenfailed %s\n", strerror(errno));
return EXIT_FAILURE;
}
int client_st = 0;//client端socket
// socklen_t len = 0;
struct sockaddr_in client_addr; //表示client端的IP地址
//void *p = &client_addr;
pthread_t thrd1, thrd2;
while(1)
{
memset(&client_addr, 0, sizeof(client_addr));
socklen_t len = sizeof(client_addr);
//accept会阻塞,直到有客户端连接过来,accept返回client的socket描述符
client_st = accept(st, (struct sockaddr *)&client_addr,&len);
if(client_st == -1)
{
printf("acceptfailed %s\n", strerror(errno));
return EXIT_FAILURE;
}
printf("accept by%s\n", inet_ntoa(client_addr.sin_addr));
pthread_create(&thrd1, NULL, recvsocket, &client_st);
pthread_create(&thrd2, NULL, sendsocket, &client_st);
}
close(st); //关闭server端listen的socket
return EXIT_SUCCESS;
}
client.c 程序:
void *recvsocket(void *arg) //接收client端socket数据的线程
{
int st = *(int *)arg;
char s[1024];
while(1)
{
memset(s, 0, sizeof(s));
int rc = recv(st, s, sizeof(s), 0);
if(rc <= 0) //如果返回小于等于0,代表socket已经关闭或者出错
break;
printf("%s\n", s);
}
return NULL;
}
void *sendsocket(void *arg) //发送server端socket数据的线程
{
int st = *(int *)arg;
char s[1024];
while(1)
{
memset(s, 0, sizeof(s));
read(STDIN_FILENO, s, sizeof(s)); //从键盘读取用户输入信息
send(st, s, strlen(s), 0);
}
return NULL;
}
int main(int arg, char *args[])
{
if(arg < 3)
return -1;
int port = atoi(args[2]);
int st = socket(AF_INET, SOCK_STREAM, 0); //初始化socket
struct sockaddr_in addr; //定义一个IP地址的结构
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET; //设置结构地址类型为TCP/IP地址
addr.sin_port = htons(port); //指定一个端口号:8080,htons:将short类型从host字节类型到net字节类型转化
addr.sin_addr.s_addr = inet_addr(args[1]); //将字符串类型的IP地址转化为int,赋给addr结构
//调用connect连接到结构addr指定的IP地址和端口号
if(connect(st, (struct sockaddr *)&addr, sizeof(addr)) == -1)
{
printf("connectfailed %s\n", strerror(errno));
return EXIT_FAILURE;
}
pthread_t thrd1, thrd2;
pthread_create(&thrd1, NULL, recvsocket, &st);
pthread_create(&thrd2, NULL, sendsocket, &st);
pthread_join(thrd1, NULL);
pthread_join(thrd2,NULL);
close(st); //关闭socket
return EXIT_SUCCESS;
}
makefile文件:
.SUFFIXES: .c .o
CC=gcc
SRCS1=client.c
SRCS2=server.c
OBJS1=$(SRCS1:.c=.o)
OBJS2=$(SRCS2:.c=.o)
EXE1=client
EXE2=server
all: $(OBJS1) $(OBJS2)
$(CC) -o $(EXE1) $(OBJS1) -lpthread
$(CC) -o $(EXE2) $(OBJS2) -lpthread
@echo '^_^^_^^_^^_^^_^^_^OK^_^^_^^_^^_^^_^^_^'
.c.o:
$(CC) -Wall -g -o $@ -c $<
clean:
-rm -f $(OBJS1) $(OBJS2)
-rm -f core*
程序运行效果如下:
先运行服务器端,server 8080(端口号)
再运行客户端,client *.*.*.* 8080 第二个参数是IP地址
此时,服务器端显示连接到此服务器的客户端的IP地址,然后就可以聊天。
上面的程序还不完善,我们想只能允许一个客户端与服务器连接的话,其函数如下:
server端:
struct ps{
int st;
pthread_t *thr;
};
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
static int status = 0;
void *recvsocket(void *arg) //接收client端socket数据的线程
{
struct ps *p = (struct ps *)arg;
int st = p->st;
char s[1024];
while(1)
{
memset(s, 0, sizeof(s));
int rc = recv(st, s, sizeof(s), 0);
if(rc <= 0) //如果返回小于等于0,代表socket已经关闭或者出错
{
break;
}
printf("%s\n", s);
}
pthread_mutex_lock(&mutex);
status = 0;
pthread_mutex_unlock(&mutex);
pthread_cancel(*(p->thr)); // 在将recvsocket线程关闭时,在这里把sendsocket线程cancel掉,被cancel掉的线程内部没有使用锁。
return NULL;
}
void *sendsocket(void *arg) //发送server端socket数据的线程
{
int st = *(int *)arg;
char s[1024];
while(1)
{
memset(s, 0, sizeof(s));
read(STDIN_FILENO, s, sizeof(s)); //从键盘读取用户输入信息
send(st, s, strlen(s), 0);
}
return NULL;
}
int main(int arg, char *args[])
{
if(arg < 2)
{
return -1;
}
int port = atoi(args[1]);
int st = socket(AF_INET, SOCK_STREAM, 0); //初始化socket
int on = 1;
if(setsockopt(st, SOL_SOCKET, SO_REUSEADDR,&on, sizeof(on)) == -1)
{
printf("setsockoptfailed %s\n", strerror(errno));
return EXIT_FAILURE;
}
struct sockaddr_in addr; //定义一个IP地址的结构
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET; //设置addr结构的属性定位为TCP/IP地址
addr.sin_port = htons(port); //将本地字节顺序转化为网络字节数据
addr.sin_addr.s_addr = htonl(INADDR_ANY); //INADDR_ANY代表这个server上所有的地址
//将IP地址与server程序绑定
if(bind(st, (struct sockaddr *)&addr, sizeof(addr)) == -1)
{
printf("connectfailed %s\n", strerror(errno));
return EXIT_FAILURE;
}
//server端开始listen,
if(listen(st, 20) == -1)
{
printf("listenfailed %s\n", strerror(errno));
return EXIT_FAILURE;
}
int client_st = 0;//client端socket
// socklen_t len = 0;
struct sockaddr_in client_addr; //表示client端的IP地址
//void *p = &client_addr;
pthread_t thrd1, thrd2;
while(1)
{
memset(&client_addr, 0, sizeof(client_addr));
socklen_t len = sizeof(client_addr);
//accept会阻塞,直到有客户端连接过来,accept返回client的socket描述符
client_st = accept(st, (struct sockaddr *)&client_addr,&len);
pthread_mutex_lock(&mutex); //为全局变量加一个互斥锁,防止与线程函数同时读写变量的冲突
status++;
pthread_mutex_unlock(&mutex); //解锁
if(status > 1) //代表这是第二个socket连接,关闭(确保只能连接一个socket)
{
close(client_st);
continue;
}
if(client_st == -1)
{
printf("acceptfailed %s\n", strerror(errno));
return EXIT_FAILURE;
}
printf("accept by%s\n", inet_ntoa(client_addr.sin_addr));
struct ps ps1;
ps1.st = client_st;
ps1.thr = &thrd2;
pthread_create(&thrd1, NULL, recvsocket, &ps1);
pthread_detach(thrd1); //设置线程为可分离
pthread_create(&thrd2, NULL, sendsocket, &client_st);
pthread_detach(thrd2); //设置线程为可分离
}
close(st); //关闭server端listen的socket
return EXIT_SUCCESS;
}
client端
void *recvsocket(void *arg) //接收client端socket数据的线程
{
int st = *(int *)arg;
char s[1024];
while(1)
{
memset(s, 0, sizeof(s));
int rc = recv(st, s, sizeof(s), 0);
if(rc <= 0) //如果返回小于等于0,代表socket已经关闭或者出错
{
break;
}
printf("%s\n", s);
}
return NULL;
}
void *sendsocket(void *arg) //发送server端socket数据的线程
{
int st = *(int *)arg;
char s[1024];
while(1)
{
memset(s, 0, sizeof(s));
read(STDIN_FILENO, s, sizeof(s)); //从键盘读取用户输入信息
send(st, s, strlen(s), 0);
}
return NULL;
}
int main(int arg, char *args[])
{
if(arg < 3)
return -1;
int port = atoi(args[2]);
int st = socket(AF_INET, SOCK_STREAM, 0); //初始化socket
struct sockaddr_in addr; //定义一个IP地址的结构
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET; //设置结构地址类型为TCP/IP地址
addr.sin_port = htons(port); //指定一个端口号:8080,htons:将short类型从host字节类型到net字节类型转化
addr.sin_addr.s_addr = inet_addr(args[1]); //将字符串类型的IP地址转化为int,赋给addr结构
//调用connect连接到结构addr指定的IP地址和端口号
if(connect(st, (struct sockaddr *)&addr, sizeof(addr)) == -1)
{
printf("connectfailed %s\n", strerror(errno));
return EXIT_FAILURE;
}
pthread_t thrd1, thrd2;
pthread_create(&thrd1, NULL, recvsocket, &st);
pthread_create(&thrd2, NULL, sendsocket, &st);
pthread_join(thrd1, NULL);
//pthread_join(thrd2, NULL);
close(st); //关闭socket
return EXIT_SUCCESS;
}
上述程序用一个全局变量,来限制连接服务器端的客户端个数,并且为了防止与线程读写数据的冲突,加了互斥锁。并且在关闭客户端时,注意将相应的所有线程关闭。注意:被cancel掉的线程没有互斥锁。