您的位置:首页 > 编程语言

socket编程之客户--服务器通信程序学习

2017-06-19 15:26 495 查看
操作系统有五大功能,简单总结为:

操作系统的五大功能:
1、进程管理
2、内存管理
3、文件系统
4、网络管理
5、设备管理


网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个socket。

网络中的进程是通过socket来通信的,那什么是socket呢?socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,都可以用“打开open –> 读写write/read –> 关闭close”模式来操作。我的理解就是Socket就是该模式的一个实现,socket即是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO、打开、关闭)

一、UNIX网络编程



在socket编程中会遇到很多函数,比如socket()、bind()、listen()等等,我会边学边记录熟悉这些函数的用法,简略的概述都在注释里,方便以后翻阅和学习

二、服务器端

#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/shm.h>

#define MYPORT  8887
#define QUEUE   20
#define BUFFER_SIZE 1024

int main()
{

int server_sockfd = socket(AF_INET,SOCK_STREAM, 0); //定义服务器端socket函数

///定义sockaddr_in
struct sockaddr_in server_sockaddr;
server_sockaddr.sin_family = AF_INET;
server_sockaddr.sin_port = htons(MYPORT);
server_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);//需要监听的端口,这里选择ANY表示所有网络端口都监听
/*
 http://blog.csdn.net/yaxiya/article/details/6722083 
htonl就是把本机字节顺序转化为网络字节顺序

h---host 本地主机
to  就是to 了
n  ---net 网络的意思
l 是 unsigned long

同理可得ntohl就是网络字节序转化为本机字节序
htonl和ntohl主要是为了防止在不同平台的机器数据存放格式不同 http://bbs.csdn.net/topics/80351112  论坛讨论ntohl和htonl
*/

/*
bind,成功返回0,出错返回-1
int bind(int sockfd,const struct sockaddr* myaddr,socklen_t addrlen)
当socket函数返回一个描述符时,只是存在于其协议族的空间中,并没有分配一个具体的协议地址(这里指IPv4/IPv6和端口号的组合),
bind函数可以将一组固定的地址绑定到sockfd上。

其中:

sockfd是socket函数返回的描述符;

myaddr指定了想要绑定的IP和端口号,均要使用网络字节序-即大端模式;

addrlen是前面struct sockaddr(与sockaddr_in等价)的长度。

*/
if(bind(server_sockfd,(struct sockaddr *)&server_sockaddr,sizeof(server_sockaddr))==-1)
{
perror("bind");
exit(1);
}

/*listen,成功返回0,出错返回-1
int listen(int sockfd, int backlog);
开始监听套接字
sockfd:套接字,成功返回后进入监听模式,当有新连接并accept后会再建立一个套接字保存新的连接;
  backlog:暂且翻译为后备连接吧!下面详细介绍此参数:

  1)  当TCP接收一个连接后(三次握手通过)会将此连接存在连接请求队列里面,并对队列个数+1,而backlog为此队列允许的最大个数,超过此值,则直接将新的连接删除,即不在接收新的连接。将这些处于请求队列里面的连接暂记为后备连接,这些都在底层自动完成,底层将连接添加到队列后等待上层来处理(一般是调用accept函数接收连接);

  2)  当上层调用accept函数接收一个连接(处于请求队列里面的后备连接),队列个数会-1;

  3)  那么这样一个加一个减,只要底层提交的速度小于上层接收的速度(一般是这样),很明显backlog就不能限制连接的个数,只能限制后备连接的个数。那为啥要用这个backlog呢?主要用于并发处理,当上层没来的及接收时,底层可以提交多个连接;

  4)  backlog的取值范围 ,一般为0-5。 http://www.cnblogs.com/mddblog/p/4492784.html */

if(listen(server_sockfd,QUEUE) == -1)   //开始监听
{
perror("listen");
exit(1);
}

///客户端套接字
char buffer[BUFFER_SIZE];
struct sockaddr_in client_addr;
socklen_t length = sizeof(client_addr);

/*成功返回非负描述字,出错返回-1
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
开始等待客户端连过来套接字
addr 和 addrlen 一般是NULL, 否则只允许addr指定的客户端连过来。

*/
int conn = accept(server_sockfd, (struct sockaddr*)&client_addr, &length);
if(conn<0)
{
perror("connect");
exit(1);
}

while(1)
{
memset(buffer,0,sizeof(buffer));//清空buffer
int len = recv(conn, buffer, sizeof(buffer),0);//recv函数返回其实际copy的字节数,http://blog.csdn.net/tiandyoin/article/details/30044781
if(strcmp(buffer,"exit\n")==0)
break;
fputs(buffer, stdout);//把接收到的内容输出到屏幕上
send(conn, buffer, len, 0);
}
close(c
4000
onn);
close(server_sockfd);
return 0;
}


三、客户端

#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/shm.h>

#define MYPORT  8887
#define BUFFER_SIZE 1024

int main()
{

int sock_cli = socket(AF_INET,SOCK_STREAM, 0);//定义socket客户端函数

/*  int socket(int domain, int type, int protocol);
创建socket, 返回文件描述符
domain: AF_INET、 AF_INET6、 AF_UNIX、 AF_UPSPEC       指明了协议族/域比如AF_INET就是协议版本4,AF_INET6就是协议版本6
type: SOCK_DGRAM、 SOCK_RAW、 SOCK_SEQPACKET、 SOCK_STREAM     type是套接口类型

SOCK_STREAM 提供有序的、可靠的、双向的和基于连接的字节流,使用带外数据传送机制,为Internet地址族使用TCP。
SOCK_DGRAM 支持无连接的、不可靠的和使用固定大小(通常很小)缓冲区的数据报服务,为Internet地址族使用UDP。

protocol: IPPROTO_IP、 IPPROTO_IPV6、 IPPROTO_ICMP、 IPPROTO_RAW、
IPPROTO_TCP、 IPPROTO_UDP    protocol:套接口所用的协议。如调用者不想指定,可用0指定,表示缺省

*/

//定义sockaddr_in结构体          http://blog.csdn.net/renchunlin66/article/details/52351751 struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof(servaddr));         //memset函数在驱动里见到过,清空某一段空间
servaddr.sin_family = AF_INET;      //sin_family指代协议族,在socket编程中只能是AF_INET
servaddr.sin_port = htons(MYPORT);  //服务器端口,避免冲突建议设置大一些,因为比较小的端口都是默认的
servaddr.sin_addr.s_addr = inet_addr("192.168.216.128");  //服务器ip

//连接服务器,成功返回0,错误返回-1
if (connect(sock_cli, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0)
{
perror("connect");
exit(1);
}
/*
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
连接指定的服务器, 其中addr指定连接服务器的IP和端口

*/

char sendbuf[BUFFER_SIZE];
char recvbuf[BUFFER_SIZE];
/*
send类似于write
int send(IN SOCKET s, IN const char FAR * buf, IN int len, IN int flags)

失败时返回 -1/SOCKET_ERROR

其中:

sockfd:发送端套接字描述符(非监听描述符)

buf:应用要发送数据的缓存

len:实际要发送的数据长度

flag:一般设置为0

recv类似于read

ssize_t recv(int sockfd,void *buf, size_t len,int flags)

其中:

sockfd:接收端套接字描述符;

buf:指定缓冲区地址,用于存储接收数据;

len:指定的用于接收数据的缓冲区长度;

flags:一般指定为0
其实在一般情况下可以互换,便于理解也可使用read和write
*/

while (fgets(sendbuf, sizeof(sendbuf), stdin) != NULL)//fgets函数在之前获取IP的时候用到过,相当于检测是否clint有写入吧。
{
send(sock_cli, sendbuf, strlen(sendbuf),0); //客户端发送
if(strcmp(sendbuf,"exit\n")==0)//在蜂鸣器中有用到strcmp,用来比较两个字符串,若完全相同则返回0
break;
recv(sock_cli, recvbuf, sizeof(recvbuf),0); //接收
fputs(recvbuf, stdout);//fputs函数向指定的文件指针中写入字符串,这里是向屏幕中输出接收到的信息

/*
stdout(Standardoutput)标准输出
stdin(Standardinput)标准输入
stderr(Standarderror)标准错误
*/

memset(sendbuf, 0, sizeof(sendbuf));//清空使用的空间
memset(recvbuf, 0, sizeof(recvbuf));
}

close(sock_cli);//关闭socket
return 0;
}


四、客户–服务器通信

服务器端接收:



客户端发送:



要先打开服务器端,然后再打开客户端连接

//http://blog.csdn.net/g_brightboy/article/details/12854117常用socket函数
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息