linux网络编程-socket
Posted luzhouxiaoshuai
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了linux网络编程-socket相关的知识,希望对你有一定的参考价值。
当客户端调用close函数的时候,服务器的read函数读到的数据是0读到文件结束通知,表示对端关闭了tcp连接
我们现实实现下面的功能:
1、tcp客户端从标准的输入流中得到输入数据发送到服务器,服务器收到数据之后,不做任何改变,将书法返回给客户端,客户端收到服务器的数据之后,在标准输出流中输出
上面代码中PF_INET和AF_INET都是一样的都是代码tcp的协议族
tcp协议对应的流式套接字,所以写成sock_STREAM
第三个参数可以写成IPPPOTO_TCP或者0都是可以的
第四个结构体中服务器绑定的地址可以使用inet_addr函数将本机的点分十进制的ip地址转换成32位的网络地址
也可以使用htonl函数将本机的任何地址转换成32位的函数,其中INADDR_ANY,表示本机的任何地址都可以
对于端口也必须是网络字节序,所以需要使用htons将本机端口转换成16位的无符号网络字节端口
Read函数
Ssize_t read(int fd,void *buf,size_t nbyte)
Read函数是负责从fd中读取内容,当读取成功时,read返回实际读取到的字节数,如果返回值是0,表示已经读取到文件的结束了,小于0表示是读取错误。
如果错误是EINTR表示在写的时候出现了中断错误,如果是EPIPE表示网络连接出现了问题。
Write函数
Ssize_t write(int fd,const void *buf,size_t nbytes);
Write函数将buf中的nbytes字节内容写入到文件描述符中,成功返回写的字节数,失败返回-1.并设置errno变量。在网络程序中,当我们向套接字文件描述舒服写数据时有两种可能:
我们来看下服务器的代码:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> /* *定义一个宏,输出错误信息并且退出 */ #define ERR_EXIT(m) \\ do \\ {\\ perror(m);\\ exit(EXIT_FAILURE);\\ }while(0) int main(int argc, char *argv[]) { int serv_sock; struct sockaddr_in serv_addr; serv_sock = socket(AF_INET, SOCK_STREAM, 0); if (serv_sock == -1) { ERR_EXIT("socket创建失败"); } memset(&serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); serv_addr.sin_port = htons(9999); if((connect(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)))<0){ ERR_EXIT("客户端connect失败"); } char revbuf[1024]; char sendbuf[1024]; memset(revbuf,0,sizeof(revbuf)); memset(sendbuf,0,sizeof(revbuf)); while((fgets(sendbuf,sizeof(sendbuf),stdin)!= NULL)){ write(serv_sock,sendbuf,strlen(sendbuf)); read(serv_sock,revbuf,sizeof(revbuf)); fputs(revbuf,stdout); //读到多少数据就给客户端返回多少字节的数据 memset(sendbuf,0,sizeof(revbuf)); memset(revbuf,0,sizeof(revbuf)); } close(serv_sock); return 0; }
服务器代码:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> /* *定义一个宏,输出错误信息并且退出 */ #define ERR_EXIT(m) \\ do \\ {\\ perror(m);\\ exit(EXIT_FAILURE);\\ }while(0) int main(int argc, char *argv[]) { int serv_sock; int clnt_sock; struct sockaddr_in serv_addr; struct sockaddr_in clnt_addr; socklen_t clnt_addr_size; serv_sock = socket(AF_INET, SOCK_STREAM, 0); if (serv_sock == -1) { ERR_EXIT("socket创建失败"); } memset(&serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); serv_addr.sin_port = htons(9999); if (bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1){ ERR_EXIT("bind失败"); } //SOMAXCON系统默认的最大的客户端的连接数据 , (listen(serv_sock, 5)表示最大允许5个客户端的连接 if (listen(serv_sock, SOMAXCONN) == -1){ ERR_EXIT("listen失败"); } clnt_addr_size = sizeof(clnt_addr); clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size); if (clnt_sock == -1){ ERR_EXIT("accept失败"); } char revbuf[1024]; while(1){ memset(revbuf,0,sizeof(revbuf)); int len = read(clnt_sock,revbuf,sizeof(revbuf)); //len读到数据的字节长度 if(len == 0){ //说明客户端终止了数据的发送 break; } fputs(revbuf,stdout); //读到多少数据就给客户端返回多少字节的数据 write(clnt_sock,revbuf,len); } close(clnt_sock); close(serv_sock); return 0; } void error_handling(char *message) { fputs(message, stderr); fputc(\'\\n\', stderr); exit(1); }
在ubuntu系统上执行编译:
gcc client.c -o client
执行客户端程序就是./client
编译服务器程序
gcc server.c -o server
执行服务器程序
./server
上面的代码只能对应一个客户端连接一个服务器,如果一个服务器要支持多个客户端的请求,请看socke(37)章节的代码
我们来看服务器的函数:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> #include <pthread.h> /* *定义一个宏,输出错误信息并且退出 */ #define ERR_EXIT(m) \\ do \\ {\\ perror(m);\\ exit(EXIT_FAILURE);\\ }while(0) void*thread_exc(void* arg){ pthread_detach(pthread_self()); //将线程设置成分离状态,避免僵尸线程 int clnt_sock = (int)arg; char revbuf[1024]; while(1){ memset(revbuf,0,sizeof(revbuf)); int len = read(clnt_sock,revbuf,sizeof(revbuf)); //len读到数据的字节长度 if(len == 0){ //说明客户端终止了数据的发送 break; } fputs(revbuf,stdout); //读到多少数据就给客户端返回多少字节的数据 write(clnt_sock,revbuf,len); } close(clnt_sock); //记得关闭线程 } int main(int argc, char *argv[]) { int serv_sock; int clnt_sock; struct sockaddr_in serv_addr; struct sockaddr_in clnt_addr; socklen_t clnt_addr_size; serv_sock = socket(AF_INET, SOCK_STREAM, 0); if (serv_sock == -1) { ERR_EXIT("socket创建失败"); } memset(&serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); serv_addr.sin_port = htons(9999); if (bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1){ ERR_EXIT("bind失败"); } //SOMAXCON系统默认的最大的客户端的连接数据 , (listen(serv_sock, 5)表示最大允许5个客户端的连接 if (listen(serv_sock, SOMAXCONN) == -1){ ERR_EXIT("listen失败"); } while(1){ //在while循环中一直等待客户端的监听 clnt_addr_size = sizeof(clnt_addr); clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size); if (clnt_sock == -1){ ERR_EXIT("accept失败"); } //每一个客户端的请求都开启一个线程进行处理 pthread_t thread_id ; int ret; //将clnt_sock通过第三个参数传递到线程函数中 if((ret = pthread_create(&thread_id,NULL,thread_exc,(void*)clnt_sock)) != 0){ ERR_EXIT("线程创建失败"); } } close(serv_sock); return 0; } void error_handling(char *message) { fputs(message, stderr); fputc(\'\\n\', stderr); exit(1); }
我们来看客户端的函数:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> /* *定义一个宏,输出错误信息并且退出 */ #define ERR_EXIT(m) \\ do \\ {\\ perror(m);\\ exit(EXIT_FAILURE);\\ }while(0) int main(int argc, char *argv[]) { int serv_sock; struct sockaddr_in serv_addr; serv_sock = socket(AF_INET, SOCK_STREAM, 0); if (serv_sock == -1) { ERR_EXIT("socket创建失败"); } memset(&serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); serv_addr.sin_port = htons(9999); if((connect(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)))<0){ ERR_EXIT("客户端connect失败"); } char revbuf[1024]; char sendbuf[1024]; memset(revbuf,0,sizeof(revbuf)); memset(sendbuf,0,sizeof(revbuf)); while((fgets(sendbuf,sizeof(sendbuf),stdin)!= NULL)){ write(serv_sock,sendbuf,strlen(sendbuf)); read(serv_sock,revbuf,sizeof(revbuf)); fputs(revbuf,stdout); //读到多少数据就给客户端返回多少字节的数据 memset(sendbuf,0,sizeof(revbuf)); memset(revbuf,0,sizeof(revbuf)); } close(serv_sock); return 0; }
对应服务器的函数:有一点很关键在创建线程的的执行函数的入口处调用 pthread_detach(pthread_self()); //将线程设置成分离状态,避免僵尸线程
第二在客户端关闭连接,服务器read字节数为0的时候,记得关闭客户端的连接
close(clnt_sock); //记得关闭线程
}
我们来看程序运行的效果
服务器端收到了客户端1和客户端2的数据
客户端1:
客户端2:
这里千万不能将线程的地址传递进行会存在多线程隐患的问题,千万不能写成(void*)&clnt_sock
void*thread_exc(void* arg){
pthread_detach(pthread_self()); //将线程设置成分离状态,避免僵尸线程
int clnt_sock = *(*int)arg;
这样会存在多线程隐患的问题,当第一个线程正在执行thread_exc执行int clnt_sock = *(*int)arg或者自己线程的sockid的时候,此时第二个线程创建了成功改变了sockid的值,第一个线程通过*(*int)arg获得的sockid就是刚刚创建的第二个线程的。所以这里存在线程安全问题,所以不能使用指针传递,所以必须使用值传递
但是上面的代码还存在一个小bug
就是将int类型强制转换成了void*类型void*)clnt_sock)存在问题,例如在64位的系统上指针void*是8个字节,int是4个字节,存在转换的问题,可以使用下面的方式进行解决
程序的代码修改如下所示:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> #include <pthread.h> /* *定义一个宏,输出错误信息并且退出 */ #define ERR_EXIT(m) \\ do \\ {\\ perror(m);\\ exit(EXIT_FAILURE);\\ }while(0) void*thread_exc(void* arg){ pthread_detach(pthread_self()); //将线程设置成分离状态,避免僵尸线程 int clnt_sock = *((int*)arg); //记得关闭指针 free(arg); char revbuf[1024]; while(1){ memset(revbuf,0,sizeof(revbuf)); int len = read(clnt_sock,revbuf,sizeof(revbuf)); //len读到数据的字节长度 if(len == 0){ //说明客户端终止了数据的发送 break; } fputs(revbuf,stdout); //读到多少数据就给客户端返回多少字节的数据 write(clnt_sock,revbuf,len); } close(clnt_sock); //记得关闭线程 } int main(int argc, char *argv[]) { int serv_sock; int clnt_sock; struct sockaddr_in serv_addr; struct sockaddr_in clnt_addr; socklen_t clnt_addr_size; serv_sock = socket(AF_INET, SOCK_STREAM, 0); if (serv_sock == -1) { ERR_EXIT("socket创建失败"); } memset(&serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); serv_addr.sin_port = htons(9999); if (bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1){ ERR_EXIT("bind失败"); } //SOMAXCON系统默认的最大的客户端的连接数据 , (listen(serv_sock, 5)表示最大允许5个客户端的连接 if (listen(serv_sock, SOMAXCONN) == -1){ ERR_EXIT("listen失败"); } while(1){ //在while循环中一直等待客户端的监听 clnt_addr_size = sizeof(clnt_addr); clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size); if (clnt_sock == -1){ ERR_EXIT("accept失败"); } //每一个客户端的请求都开启一个线程进行处理 pthread_t thread_id ; int ret; //将clnt_sock通过第三个参数传递到线程函数中 int * p = (int*)malloc(sizeof(int)); *p = clnt_sock; if((ret = pthread_create(&thread_id,NULL,thread_exc, p ))!= 0){ ERR_EXIT("线程创建失败"); } } close(serv_sock); return 0; } void error_handling(char *message) { fputs(message, stderr); fputc(\'\\n\', stderr); exit(1); }
以上是关于linux网络编程-socket的主要内容,如果未能解决你的问题,请参考以下文章