linux网络编程-socket-函数及TCP通信实现

套接字概念

在通信过程中,套接字一定是成对出现的即服务器一个,客户端一个。Socket本身有“插座”的意思,在Linux环境下,用于表示进程间网络通信的特殊文件类型。本质为内核借助缓冲区形成的伪文件。
既然是文件,那么理所当然的,我们可以使用文件描述符引用套接字

在这里插入图片描述
虽然仅有一个套接字,但这个套接字有两个缓冲区,一端的发送缓冲区对应对端的接收缓冲区,那么不看细节,那就是管道从一端流到另一端。
一个服务端和一个客户端连接有三个socket,两个在服务端,一个在客户端,服务端使用socket函数创建完socket并bind地址结构在connect设置监听上限 然后交给accept,在有连接建立后,accept将新建一个socket,旧socket将继续去监听。

预备知识

网络字节序和基本转换函数

TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节。
如果本地存储使用小端字节序,需要进行网络字节序和主机字节序的转换。

#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong); //不经常使用,使用inet_pton
uint16_t htons(uint16_t hostshort);//客户端和服务端指定端口时使用
uint32_t ntohl(uint32_t netlong);  //不经常使用,使用inet_ntop
uint16_t ntohs(uint16_t netshort);//服务端获取客户端端口时使用

htonl 本地ip转网络ip
htons 本地端口转网络端口
ntohl 网络ip转本地ip
ntohs 网络端口转本地端口
n代表网络,h代表本地,l代表ip,s代表端口,ip不是点分十进制

上述在转换ip时还需要将点分十进制string类型转换成int类型,比较麻烦
并且只能处理ipv4的地址,现在在转换ip时使用下面的函数

	#include <arpa/inet.h>
	int inet_pton(int af, const char *src, void *dst);
	const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);

inet_pton,这里的p表示点分十进制的ipv4或者ipv6,n表示网络字节序,因此这个命令表示将点分十进制的ip转换成网络字节序。
inet_ntop 顾名思义将网络字节序转换成点分十进制的形式。
具体看下面的ip地址转换函数。

上述转换函数在sockaddr_in中使用

IP地址转换函数

在linxu使用man inet_pton查看函数原型

//本地字节序(string)转换成网络字节序
int inet_pton(int af, const char *src, void *dst);

af表示使用的ip类型,有 AF_INET和 AF_INET6
src:(传入)表示ip地址(点分十进制)
dst:(传出)转换后的网络字节序的ip地址
返回值:1:成功 ; 0:表示src不是一个有效的ip地址;-1:失败

在linux使用man inet_ntop查看函数原型

//网络字节序(二进制)转换成本地字节序(string)
#include <arpa/inet.h>
const char *inet_ntop(int af, const void *src,char *dst, socklen_t size);

af表示使用的ip类型,有 AF_INET和 AF_INET6
src:(传入)网络字节序ip地址
dst:(传出)本地字节序(string)
size: dst的大小
返回值:成功返回一个dst,失败返回空

sockaddr数据结构

在这里插入图片描述

//一般代码
struct sockaddr_in addr;
bind(fd,(struct sockaddr*)&addr,);//第三个参数是size,这里先不说
//强制将sockaddr_in转换成sockaddr类型

man有9卷,查看ip看第7卷,即man 7 ip

在这里插入图片描述
定义的时候,由于addr.sin_addr是一个结构体,应该给结构体里的数据赋值而不是给结构体,这里注意。

struct sockaddr_in addr;
addr.sin_family= AF_INET;//或者是 AF_INET6
addr.sin_port=htons(9527);//这里随便指定了一个端口  本地端口转网络端口h本地 n网络 s端口
addr.sin_addr.s_addr=inet_pton(AF_INET,"192.168.1.101",dst);

上述的addr.sin_addr.s_addr可以那样写,也可以

int dst;
inet_pton(AF_INET,"192.168.1.101",(void *)&dst);
addr.sin_addr.s_addr=dst;

但一般都是使用宏
在这里插入图片描述

INADDR_ANY;//取出系统中有效的任意ip地址,取出的是二进制类型
//因此还要转换成网络字节序
addr.sin_addr.s_addr=(htonl)INADDR_ANY;

网络套接字函数

在这里插入图片描述
服务端
socket()产生一个套接字,由文件描述符关联
bind()将套接字和ip、端口绑定
listen()设置同时监听上限(说白了就是参数设置,并不是监听)
accept()阻塞监听客户端连接
套接字给了accept作为参数,当与客户端建立连接后会返回一个新的socket,那个旧的socket会被解放出来继续监听,这个旧socket就像酒店迎宾小姐,引着人进入酒店后会重新去监听。
read()读数据
write()回写数据
close()关闭连接

客户端
socket()创建socket
connect()socket要连接的ip和端口
write()
read()
close()

socket()

man socket

  #include <sys/types.h>          /* See NOTES */
  #include <sys/socket.h>
  int socket(int domain, int type, int protocol);

功能:socket 创建一个套接字
domain:常见下面的三种
在这里插入图片描述
type:指定传输的协议 SOCK_STREAM / SOCK_DGRAM
在这里插入图片描述
protocol: 选用的协议中代表协议 默认传0,传0便是根据type指定的选协议
SOCK_STREAM 便是TCP,SOCK_DGRAM便是UDP
返回值:成功返回一个新套接字对应的文件描述符 ,失败是返回-1
在这里插入图片描述

fd= socket(AF_INET, SOCK_STREAM , 0);

bind()

man 2 bind

 #include <sys/types.h>          /* See NOTES */
 #include <sys/socket.h>

 int bind(int sockfd, const struct sockaddr *addr,
                socklen_t addrlen);

功能:给socket绑定一个ip+端口(地址结构)
sockfd:socket()的返回值
addr:先初始化sockaddr_in,再强转成sockaddr ,下面是个例子

下面初始化sockaddr_in

struct sockaddr_in addr;
addr.sin_family= AF_INET;//或者是 AF_INET6
addr.sin_port=htons(9527);//这里随便指定了一个端口  本地端口转网络端口h本地 n网络 s端口
addr.sin_addr.s_addr=(htonl)INADDR_ANY;

强转时

(struct sockaddr *)addr

addrlen:地址结构的大小
返回值:成功返回0 失败返回-1 error

listen()

man listen

 #include <sys/types.h>          /* See NOTES */
 #include <sys/socket.h>
 int listen(int sockfd, int backlog);

功能:listen设置同时与服务器建立连接的上限数(同时进行三次握手的客户端数量)
sockfd:socket
backlog :上限值,上限值不能超过128
返回值:成功返回0 失败返回-1 error

accept()

man 2 accept

 #include <sys/types.h>          /* See NOTES */
 #include <sys/socket.h>
 int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

功能:阻塞等待客户端建立连接,成功返回一个与客户端成功连接的新socket文件描述符

sockfd:给个socket,这里注意,新产生的socket要依托于旧socket,主要是用旧socket的ip和端口
addr:传出参数,传出成功与服务器建立连接的那个客户端的地址结构(ip+端口号)
addrlen:传入传出参数。进去时是addr的大小,出是客户端addr实际大小

socklen_t clit_addr_len =sizeof(addr);
//第三个参数如下面这样写
&clit_addr_len 

返回值:成功时 返回能够与服务器进行数据通信的socket对应的文件描述符 失败:-1 error

connect()

man 2 connect

  #include <sys/types.h>          /* See NOTES */
  #include <sys/socket.h>
  int connect(int sockfd, const struct sockaddr *addr,
                   socklen_t addrlen);

使用现有的socket与服务器建立连接
sockfd:socket
addr:传入参数,是服务器的地址结构(ip+端口)
addrlen:服务器地址结构的大小

客户端不需要绑定自己的ip和端口,虽然可以使用bind绑定
如果没有绑定,那么会采用“隐式绑定”(系统来干的)

实例

在这里插入图片描述
注意上图强转时是传的地址,上图有个错误,在accept函数里面,第二个参数应该是用来保存客户端信息的。具体代码看后面。
在这里插入图片描述
编译

gcc tcpserver.c -o server -Wall -g

运行server,这里暂时使用脑残命令测试
在这里插入图片描述
可以看到运行成功
下面是代码,下面代码加了显示客户端ip地址和端口的功能

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<errno.h>
#include<pthread.h>
#include<ctype.h>
#include<sys/socket.h>
#include<arpa/inet.h>


void sys_err(const char *str)
{
 perror(str);
 exit(1);
}


int main(int argc,char *argv[])
{
  int lfd=0;
  lfd=socket(AF_INET,SOCK_STREAM,0);
  if(lfd==-1)
  {
     sys_err("socket error");
  }
  
  //bind函数绑定ip和端口
  struct sockaddr_in server_addr;
  server_addr.sin_family=AF_INET;
  server_addr.sin_port=htons(22222);
  server_addr.sin_addr.s_addr=(htonl)INADDR_ANY;
  bind(lfd,(struct sockaddr *)&server_addr,sizeof(server_addr));
  
  //设置同时监听个数
  int lisRetrun=listen(lfd,128);
  if(lisRetrun==-1)
  {
      sys_err("listen error");
  }
  
  //accept
  struct sockaddr_in client_addr;
  socklen_t cliAddrLen=sizeof(client_addr);
  int cfd=accept(lfd,(struct sockaddr *)&client_addr,&cliAddrLen);
  if(cfd==-1)
  {
      sys_err("accept error");
  }
  
  char client_ip[1024];
  printf("\n client ip:%s  port:%d\n",
                inet_ntop(AF_INET,&client_addr.sin_addr,client_ip,sizeof(client_ip)),
                ntohs(client_addr.sin_port)
                    );
  
  char buf[BUFSIZ];
  while(1)
  {
      
     int ret= read(cfd,buf,sizeof(buf));
      
      int i;
     for(i=0;i<ret;i++)
      {
       printf("%c",buf[i]);
       buf[i]=toupper(buf[i]);
      }
  
      write(cfd,buf,ret);
  
  }
  close(lfd);
  close(cfd);
  
  return 0;
  

}

客户端代码

在这里插入图片描述

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<errno.h>
#include<pthread.h>
#include<ctype.h>
#include<sys/socket.h>
#include<arpa/inet.h>


void sys_err(const char *str)
{
 perror(str);
 exit(1);
}


int main(int argc,char *argv[])
{
  
  
  int cfd=socket(AF_INET,SOCK_STREAM,0);
  if(cfd==-1)
  {
      sys_err("create socket error");
  }
  
  struct sockaddr_in serv_addr;//服务器地址结构
  serv_addr.sin_family=AF_INET;
  serv_addr.sin_port=htons(22222);//设置端口
  int dst;
  inet_pton(AF_INET, "127.0.0.1", (void *)&dst);
  serv_addr.sin_addr.s_addr=dst;
  
  int conResult=connect(cfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr));
  if(conResult)
  {
      sys_err("connect error");
  }
  
  
  int count=10;
  char buf[1024];
  while(--count)
  {
     write(cfd,"hello",5); 
    int ret= read(cfd,buf,sizeof(buf));
     int j;
     for(j=0;j<ret;j++)
     {
         printf("%c",buf[j]);
     }
     sleep(1);
     printf("\n");
  }
  
  close(cfd);
  return 0;
  
}

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

贪睡的蜗牛

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值