当客户端调用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() int main(int argc, char *argv[])
{
int serv_sock;
struct sockaddr_in serv_addr; serv_sock = socket(AF_INET, SOCK_STREAM, );
if (serv_sock == -)
{
ERR_EXIT("socket创建失败");
} memset(&serv_addr, , 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(); if((connect(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)))<){
ERR_EXIT("客户端connect失败");
} char revbuf[];
char sendbuf[];
memset(revbuf,,sizeof(revbuf));
memset(sendbuf,,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,,sizeof(revbuf));
memset(revbuf,,sizeof(revbuf));
}
close(serv_sock); return ;
}

服务器代码:

#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() 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, );
if (serv_sock == -)
{
ERR_EXIT("socket创建失败");
} memset(&serv_addr, , sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_addr.sin_port = htons(); if (bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -){
ERR_EXIT("bind失败");
}
//SOMAXCON系统默认的最大的客户端的连接数据 , (listen(serv_sock, 5)表示最大允许5个客户端的连接
if (listen(serv_sock, SOMAXCONN) == -){
ERR_EXIT("listen失败");
}
clnt_addr_size = sizeof(clnt_addr);
clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size);
if (clnt_sock == -){
ERR_EXIT("accept失败");
} char revbuf[];
while(){
memset(revbuf,,sizeof(revbuf));
int len = read(clnt_sock,revbuf,sizeof(revbuf)); //len读到数据的字节长度
if(len == ){ //说明客户端终止了数据的发送
break;
}
fputs(revbuf,stdout);
//读到多少数据就给客户端返回多少字节的数据
write(clnt_sock,revbuf,len);
}
close(clnt_sock);
close(serv_sock); return ;
} void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit();
}

在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() void*thread_exc(void* arg){
pthread_detach(pthread_self()); //将线程设置成分离状态,避免僵尸线程
int clnt_sock = (int)arg;
char revbuf[];
while(){
memset(revbuf,,sizeof(revbuf));
int len = read(clnt_sock,revbuf,sizeof(revbuf)); //len读到数据的字节长度
if(len == ){ //说明客户端终止了数据的发送
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, );
if (serv_sock == -)
{
ERR_EXIT("socket创建失败");
} memset(&serv_addr, , sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_addr.sin_port = htons(); if (bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -){
ERR_EXIT("bind失败");
}
//SOMAXCON系统默认的最大的客户端的连接数据 , (listen(serv_sock, 5)表示最大允许5个客户端的连接
if (listen(serv_sock, SOMAXCONN) == -){
ERR_EXIT("listen失败");
} while(){ //在while循环中一直等待客户端的监听 clnt_addr_size = sizeof(clnt_addr);
clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size);
if (clnt_sock == -){
ERR_EXIT("accept失败");
}
//每一个客户端的请求都开启一个线程进行处理
pthread_t thread_id ;
int ret;
//将clnt_sock通过第三个参数传递到线程函数中
if((ret = pthread_create(&thread_id,NULL,thread_exc,(void*)clnt_sock)) != ){
ERR_EXIT("线程创建失败");
} } close(serv_sock); return ;
} void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit();
}

我们来看客户端的函数:

#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() int main(int argc, char *argv[])
{
int serv_sock;
struct sockaddr_in serv_addr; serv_sock = socket(AF_INET, SOCK_STREAM, );
if (serv_sock == -)
{
ERR_EXIT("socket创建失败");
} memset(&serv_addr, , 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(); if((connect(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)))<){
ERR_EXIT("客户端connect失败");
} char revbuf[];
char sendbuf[];
memset(revbuf,,sizeof(revbuf));
memset(sendbuf,,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,,sizeof(revbuf));
memset(revbuf,,sizeof(revbuf));
}
close(serv_sock); return ;
}

对应服务器的函数:有一点很关键在创建线程的的执行函数的入口处调用 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() void*thread_exc(void* arg){
pthread_detach(pthread_self()); //将线程设置成分离状态,避免僵尸线程
int clnt_sock = *((int*)arg);
//记得关闭指针
free(arg);
char revbuf[];
while(){
memset(revbuf,,sizeof(revbuf));
int len = read(clnt_sock,revbuf,sizeof(revbuf)); //len读到数据的字节长度
if(len == ){ //说明客户端终止了数据的发送
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, );
if (serv_sock == -)
{
ERR_EXIT("socket创建失败");
} memset(&serv_addr, , sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_addr.sin_port = htons(); if (bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -){
ERR_EXIT("bind失败");
}
//SOMAXCON系统默认的最大的客户端的连接数据 , (listen(serv_sock, 5)表示最大允许5个客户端的连接
if (listen(serv_sock, SOMAXCONN) == -){
ERR_EXIT("listen失败");
} while(){ //在while循环中一直等待客户端的监听 clnt_addr_size = sizeof(clnt_addr);
clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size);
if (clnt_sock == -){
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 ))!= ){
ERR_EXIT("线程创建失败");
} } close(serv_sock); return ;
} void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit();
}

linux网络编程-socket(2)的更多相关文章

  1. linux网络编程-socket(37)

    在编程的时候需要加上对应pthread开头的头文件,gcc编译的时候需要加了-lpthread选项 第三个参数是线程的入口参数,函数的参数是void*,返回值是void*,第四个参数传递给线程函数的参 ...

  2. Linux网络编程socket选项之SO_LINGER,SO_REUSEADDR

    from http://blog.csdn.net/feiyinzilgd/article/details/5894300 Linux网络编程中,socket的选项很多.其中几个比较重要的选项有:SO ...

  3. Linux网络编程-----Socket地址API

    (1) 通用socket地址 socket网络编程接口中表示socket地址的是结构体sockaddr,其定义如下: #include<bits/socket.h> struct sock ...

  4. Linux网络编程--socket

    1.socket的核心思想是,作为服务器间的进程间通信的最底层的实现,常用的大部分网络协议都是基于socket实现. 2.socket 是如何与最终的低层收发包建立联系的? 3.socket 是如何与 ...

  5. Linux网络编程socket错误分析

    socket错误码: EINTR: 阻塞的操作被取消阻塞的调用打断.如设置了发送接收超时,就会遇到这种错误. 只能针对阻塞模式的socket.读,写阻塞的socket时,-1返回,错误号为INTR.另 ...

  6. Linux入门培训教程 linux网络编程socket介绍

    一.概念介绍 网络程序分为服务端程序和客户端程序.服务端即提供服务的一方,客户端为请求服务的一方.但实际情况是有些程序的客户端.服务器端角色不是这么明显,即互为Linux培训 客户端和服务端. 我们编 ...

  7. linux网络编程-socket(1)

    上面是对应的IpV4的地址结构: sin_len整个结构的大小 sin_family协议族,对应Tcp固定为AF_INET,除了tcp协议外还支持unix域协议等 sin_port socket通信的 ...

  8. linux网络编程-socket(36)

    进程是程序的一次动态执行的过程,进程是短暂的. 一个程序可以对应多个进程,可以打开多个记事本程序,存在多个进程. 线程是进程内部中的控制序列,一个进程至少有一个执行线路. 一个进程可以存在多个线程

  9. linux网络编程-(socket套接字编程UDP传输)

    今天我们来介绍一下在linux网络环境下使用socket套接字实现两个进程下文件的上传,下载,和退出操作! 在socket套接字编程中,我们当然可以基于TCP的传输协议来进行传输,但是在文件的传输中, ...

随机推荐

  1. 最小生成树——Prim算法理解

    背景:本文是在小甲鱼数据结构教学视频中的代码的基础上,添加详细注释而完成的.该段代码并不完整,仅摘录了核心算法部分,结合自己的思考,谈谈理解. Prim算法理解: 如图(摘录自小甲鱼教学视频中的图片) ...

  2. PHP获取临时文件的目录路径

    PHP获得临时文件的文件目录相对路径,能够 根据tempnam()和sys_get_temp_dir()函数来完成. 下边我们运用简单的代码实例,给大伙儿介绍PHP获得临时文件的文件目录相对路径的方式 ...

  3. 微信小程序支付(企业支付给用户零钱)

    内容摘要:本案例客户端支付方式为微信小程序支付(JSAPI).商户运营一段时间后,在微信商户平台开通企业支付服务后,即可调用微信支付提供的企业付款接口将佣金等金额通过微信零钱返现给C端用户零钱. 服务 ...

  4. python3 pymysql查询结果包含字段名

    python2使用MySQLdb模块进行连接mysql数据库进行操作:python3则使用pymysql模块进行连接mysql数据库进行操作:两者在语法上有稍微的差别,其中就包括查询结果包含字段名,具 ...

  5. Java实现 LeetCode 805 数组的均值分割 (DFS+分析题)

    805. 数组的均值分割 给定的整数数组 A ,我们要将 A数组 中的每个元素移动到 B数组 或者 C数组中.(B数组和C数组在开始的时候都为空) 返回true ,当且仅当在我们的完成这样的移动后,可 ...

  6. Java实现蓝桥杯模拟约数的个数

    问题描述 1200000有多少个约数(只计算正约数). 答案提交 这是一道结果填空的题,你只需要算出结果后提交即可.本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分. pac ...

  7. java实现海盗比酒量

    ** 海盗比酒量** 有一群海盗(不多于20人),在船上比拼酒量.过程如下:打开一瓶酒,所有在场的人平分喝下,有几个人倒下了.再打开一瓶酒平分,又有倒下的,再次重复- 直到开了第4瓶酒,坐着的已经所剩 ...

  8. java实现第四届蓝桥杯组素数

    组素数 题目描述 素数就是不能再进行等分的数.比如:2 3 5 7 11 等. 9 = 3 * 3 说明它可以3等分,因而不是素数. 我们国家在1949年建国.如果只给你 1 9 4 9 这4个数字卡 ...

  9. rpm安装Clickhouse

    1. 下载相关安装包 在opt目录下创建clickhouse目录,方便下载文件 Cd /opt/clickhouse  一次执行一下命令 ① wget --content-disposition ht ...

  10. 用mvc框架查询数据库数据

    介绍下mvc框架,mvc框架一种软件设计典范,用一种业务逻辑.数据.界面显示分离的方法组织代码,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑. 首先我们 ...