一个简单的socket客户端和服务端的例子
网络编程如TCP socket编程 位于应用层跟传输层之间, 通过socket编程让人们解脱与传输层到网络层跟链路层的传输, 我们只需要设置好socket编程接口就好,
1.socket 建立socket编程对象, 设置好是IPV4 还是IPV6 以及是TCP 还是UDP 传输等属性
2.bind 为我们建好的socket绑定好IP地址(用来区分计算机) 、端口号(用来区分到底是目标计算机的那个应用程序,数据传输过来时先被目标电脑的NIC卡网络接口卡接收,里面含有端口号,一个套接字对于一个端口号这样才可以找到应用程序,端口号是16位构成 0-65535,但0-1023一般系统用了。虽然不同TCP 连接端口号不能重复,但是TCP跟UDP传输可以用同一个端口号),计算机想网络通信必须要有自己的唯一IP地址,交换机连接局域网(LAN)内多个节点,用于局域网内的数据交换,也就是组建局域网用。 路由器则是连接局域网和广域网(WAN)的,用于不同网段之间的数据交换,局域网内各节点都是通过路由器和外网进行信息交换的。现在的简易路由器通常都还带有交换机(确切讲是集线器)的功能(1个WAN口 + 4个LAN口)如宿舍常用的TPLink。 一般我们用系统自带的设置好IP跟Port,
CPU 在内存保存数据时 有大端序 高位字节存放到低位地址。 小端序 高位字节存放到高位地址 俩中方式。 网络传输约定用大端序。
字节序转换, htons ntohs n代表network网络 h待变host主机 h to n short htonl ntohl
根据数据传输方式不同,TCP 流传输,面向连接的传输。 UDP 面向信息的传输。 TCP/IP协议族设计到理论7层实际4层 由上到下很多层面的知识,为了传输每层实现规范化的接口编程。
链路层 定义实现网卡跟LAN WAN 等网络标准。 IP层 面向消息不可靠传输自动规划路线进行传输,
TCP/UDP层 IP层只是负责数据传输但是无安全跟顺序保证 简言之就是只管努力送到,为了保证安全引入了 TCP 三次握手四次挥手 数据重传
应用层 上面的三层都是在套接字内部实现的,网络编程大部分内容也是关于应用层的编写实现。
3.listen 进入等待连接请求状态, 服务器端 调用了listen 客户端才能调用connect函数, 服务器处于等待连接请求状态
4.accept 接收client端socket请求,accept()函数返回值为自动创建的socket 用来进行数据I/O 操作,
3.listen 进入等待连接请求状态, 服务器端 调用了listen 客户端才能调用connect函数, 服务器处于等待连接请求状态
4.accept 接收client端socket请求,accept()函数返回值为自动创建的socket 用来进行数据I/O 操作,
有时为了服务器端一直可以接收数据可以用while(1)循环 accept后面部分, 实现循环回声客户端,但由于是Client发给server端 要等待server端传回来数据,等待多久? 我们可以先接收数据的大小 然后没接收全的时候 一直接受ing。
有时为了服务器端一直可以接收数据可以用while(1)循环 accept后面部分, 实现循环回声客户端,但由于是Client发给server端 要等待server端传回来数据,等待多久? 我们可以先接收数据的大小 然后没接收全的时候 一直接受ing。
TCP (流控制是关键 一对一) 会自动控制数据流大小,有滑动窗口协议,我们write时 是把数据输到缓冲区 。TCP三次握手四次挥手 SYN ACK 。而关于UDP传输没有listen 跟accept 步骤。用sendto 跟recvfrom实现数据传输。TCP时client端用connect实现IP port的连接,UDP的时在sendto 设置IP port 如果没有则自动分配, UDP传输发几次就要收几次。UDP 虽然没有listen跟accept但是每次用时都要给套接字注册IP Port 然后传输data 删除注册的IP Port 。 为了减少耗时我们可以自己给UDP设置创建已经连接UDP 套接字。
TCP (流控制是关键 一对一) 会自动控制数据流大小,有滑动窗口协议,我们write时 是把数据输到缓冲区 。TCP三次握手四次挥手 SYN ACK 。而关于UDP传输没有listen 跟accept 步骤。用sendto 跟recvfrom实现数据传输。TCP时client端用connect实现IP port的连接,UDP的时在sendto 设置IP port 如果没有则自动分配, UDP传输发几次就要收几次。UDP 虽然没有listen跟accept但是每次用时都要给套接字注册IP Port 然后传输data 删除注册的IP Port 。 为了减少耗时我们可以自己给UDP设置创建已经连接UDP 套接字。
Linux 调用close Windows调用closesocket 都是直接把socket建立的双向通道断开了,为了优雅断开引入Half-close半关闭也就是Server端发送完数据了 如果client直接close client发送的感谢言语都无法再传给server, 而如果我们只调用半关闭 让server无法发数据可以接收 还可以收到感谢信。半关闭函数为shutdown 函数 可以指定socket跟关闭方式。
Linux 调用close Windows调用closesocket 都是直接把socket建立的双向通道断开了,为了优雅断开引入Half-close半关闭也就是Server端发送完数据了 如果client直接close client发送的感谢言语都无法再传给server, 而如果我们只调用半关闭 让server无法发数据可以接收 还可以收到感谢信。半关闭函数为shutdown 函数 可以指定socket跟关闭方式。
DNS是IP地址和域名进行相互转换的系统,核心是DNS服务器。ping www.baidu.com 出现IP 浏览器输入IP 也可以到达百度首页,域名不可变一般 而IP地址可以变化。生活中用域名方便仅此而已, 用域名获得IP gethostbyname(char* hostname)返回hostent(域名对应的结构体) 同时还有一个gethostbyaddr 根据IP获取域名。先阶段写的程序都是创建套接字后直接使用,但是有时我们可以对套接字的特性进行一些操作。
DNS是IP地址和域名进行相互转换的系统,核心是DNS服务器。ping www.baidu.com 出现IP 浏览器输入IP 也可以到达百度首页,域名不可变一般 而IP地址可以变化。生活中用域名方便仅此而已, 用域名获得IP gethostbyname(char* hostname)返回hostent(域名对应的结构体) 同时还有一个gethostbyaddr 根据IP获取域名。先阶段写的程序都是创建套接字后直接使用,但是有时我们可以对套接字的特性进行一些操作。
网络编程就是通过编程让电脑跟电脑之间实现连接通信,一般情况下操作系统会提供套接字socket 这样的部件 来实现网络编程 我们按照框架填写参数就好。
网络编程中最基础的就是socket的操作,这里记录一下socket的基础操作有哪些,分别是什么作用,最后以一个简单的客户端和服务端例子收尾。
socket是什么?
socket起源于Unix,秉承着一切皆文件的思想,socket也是 打开 -读写- 关闭 这样的模式的一个实现。socket用于不同主机间进程的通信,而每个进程由 所使用的协议,Ip,端口号,三者决定,有兴趣的可以百度一下 多路分解和多路复用。
让我们看一下socket在TCP/IP中所处的位置
可以看到,socket位于应用层与传输层之间,作为一层抽象层把tcp/IP的复杂操作抽象成了一些简单的接口供应用层来调用。
socket的基本操作
主要介绍如下几个函数
int socket(int domain , int type, int protocol)
该函数创建一个socket套接字,返回一个socket描述符,相当于对该套接字的引用,在接下来的函数中将会用到它。
我们来看看各个参数,这里只介绍一些楼主使用过的
domain : 协议族,常用的就是AF_INET,AF_INET6,AF_LOCAL等几种(还有不少),它最关键的作用是指定了地址格式,比如说AF_INET(事实上楼主至今也只用了它..),它决定了该socket套接字的地址必须使用 32位的ipv4地址和16位的port号
type :类型,当然指的就是创建的socket类型了,常见的有如下两种,SOCK_STREAM,SOCK_DGRAM(还有不少),分别对应TCP套接字和UDP套接字,至于tcp和udp协议,大家可以去看一下我之前的博文关于tcp和udp的一些事。
protocol: 协议,有IPPROTO_TCP,IPPROTO_UDP等几种(还有不少),显然就是对应于传输层TCP和UDP协议,这里我经常用0,当传入0的时候,会使用socket类型对应的默认传输协议
int bind(int sockfd, const struct sockaddr* addr, socklen_t addrlen)
该函数用于给socket绑定一个地址,之前说过了不同的协议族有对应不同的地址,我们以AF_INET,即ipv4+port来解释,先看看sockaddr结构体有哪些成员
struct sockaddr {
sa_family_t sa_family; /* address family, AF_xxx */
char sa_data[14]; /* 14 bytes of protocol address */
}共16个字节
但是我们传入的参数一般是 sockaddr_in结构体,我们来看看sockaddr_in结构体的成员有哪些
struct sockaddr_in
{
__SOCKADDR_COMMON (sin_);
in_port_t sin_port; /* Port number. */
struct in_addr sin_addr; /* Internet address. */
/* Pad to size of `struct sockaddr\'. */
unsigned char sin_zero[sizeof (struct sockaddr) -
__SOCKADDR_COMMON_SIZE -
sizeof (in_port_t) -
sizeof (struct in_addr)];
/* 字符数组sin_zero[8]的存在是为了保证结构体struct sockaddr_in的大小和结构体struct sockaddr的大小相等 */
};
其实也就是方便我们使用,可以直接指定ip和port(当然这里有主机字节序和网络字节序的问题),而不需要去操作字符串了
int listen(int sockfd,int backlog)
该函数在服务端编程会用到,用于监听某个端口,至于是哪个就看你传入进去的sockfd设置的是哪一个了,backlog参数用于指定等待连接建立的socket队列的最大长度,成功返回0,失败为-1.
int accept(int sockfd, struct sockaddr* addr,socklen_t addrlen)
该函数返回与客户端建立连接的socket描述符,注意这跟监听socket不同,这是新建的一个用于与客户端通信的socket。该函数会一直阻塞到接收到一个连接为止。
第一个参数是监听socket的描述符,后面2个参数用于接受客户端的地址和地址长度。
上面2个函数都是在服务端编程中用于监听端口,被动创建与客户端的连接时,需要用到的,下面这个函数一般在客户端中需要用到的用于主动创建连接的
int connect(int sockfd,const struct sockaddr* addr,socklen_t addrlen )
第一个参数是客户端的socket描述符,第二个参数是服务器的地址,第三个当然还是地址长度,成功的时候返回0,失败则返回-1,该函数在服务器accept后,数据到达时会返回,具体连接过程,可以百度三次握手,也可以看看我写过的tcp的一些事。
OK,连接已经建立好了,现在要进行通讯了,
常用的函数有如下4个
int read(int sockfd,void* buf ,ssize_t count)
int write(int sockfd,void* buf,ssize_t count)
int recv(int sockfd,void* buf,ssize_t count,int flags)
int send(int sockfd,void* buf,ssize_t count,int flags)
这4个函数如何使用,请参照这篇文章http://blog.csdn.NET/u011408355/article/details/45921541
server端
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <string.h>
#include <arpa/inet.h>
#define PORT 6888
int main()
{
int serv_sock;
int client_sock;
struct sockaddr_in server; //设置sock地址信息
if(-1==(serv_sock=socket(AF_INET, SOCK_STREAM, IPPROTO_TCP))) // 创建SOCKET 判定用何种协议族, 用TCP/UDP类型, 针对第二个参数设置不同协议 电话购买
{
printf("Create Socket Error\n");
}
memset(&server,0,sizeof(server));
server.sin_family=AF_INET;
server.sin_addr.s_addr=inet_addr("127.0.0.1");
server.sin_port=htons(PORT);
if(-1==bind(serv_sock, (struct sockaddr*)&server, sizeof(server))) //链接IP PORT 该函数用于给socket绑定一个地址 电话号码分配
{
printf("Bind Error\n");
}
printf("Start Listen\n");
if( -1==listen(serv_sock, 5)) //监听 该函数在服务端编程会用到,用于监听某个端口,至于是哪个就看你传入进去的第一个参数设置的是哪一个了,
//第二个参数用于指定等待连接建立的socket队列的最大长度 电话线路连接
{
printf("Listen Error\n");
exit(0);
}
while(1)
{
if( -1==(client_sock=accept(serv_sock,NULL,NULL) )) //接收 该函数返回与客户端建立连接的socket描述符,注意这跟监听socket不同,
//这是新建的一个用于与客户端通信的socket。该函数会一直阻塞到接收到一个连接为止。
{
printf("Accept Error\n");
continue;
}
char buf[2048+1];
memset(buf,0, sizeof(buf));
int n = recv(client_sock ,buf, 2048, 0); //接受客户端信息 把信息放入到buf中, 返回收到的字符长度
printf("Recv msg form cilent, %d byte: ",n);
printf("%s\n",buf);
memset(buf ,0, sizeof(buf)); //初始化数组
strcpy(buf,"The Server has get the message");
send(client_sock, buf, strlen(buf), 0); //将信息送给客户端
}
close(client_sock);
close(serv_sock);
return 0;
}
client 端
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <string.h>
#include <arpa/inet.h>
#define PORT 6888
int main()
{
int conndfd;
struct sockaddr_in serverAddr;
memset(&serverAddr,0,sizeof(serverAddr));
serverAddr.sin_family=AF_INET;
serverAddr.sin_addr.s_addr=inet_addr("127.0.0.1");
serverAddr.sin_port=htons(PORT);
while(1)
{
if(-1==(conndfd=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP))) //客户端 创建
{
printf("Create Socket Error\n");
}
if(-1==connect(conndfd,(struct sockaddr*)&serverAddr,sizeof(serverAddr))) //客户端建立连接
{
printf("Connetc Error\n");
exit(0);
}
char buf[2048+1];
memset(buf, 0, sizeof(buf));
char Message[256];
printf("please input message:");
scanf("%s",Message);
printf("Connect Success Lets communicate!\n");
strcpy(buf, Message);
int n=send(conndfd, buf, strlen(buf),0); //将数据发送给服务器端
printf("Send %d byte to Server\n",n);
memset(buf,0,sizeof(buf));
n=recv(conndfd, buf, 2048, 0); //接受服务器端 发过来的信息
printf("Recv %d byte from Server:",n);
printf("%s\n",buf);
close(conndfd);
}
return 0;
}